Kniga-Online.club
» » » » Миран Липовача - Изучай Haskell во имя добра!

Миран Липовача - Изучай Haskell во имя добра!

Читать бесплатно Миран Липовача - Изучай Haskell во имя добра!. Жанр: Программирование издательство -, год 2004. Так же читаем полные версии (весь текст) онлайн без регистрации и SMS на сайте kniga-online.club или прочесть краткое содержание, предисловие (аннотацию), описание и ознакомиться с отзывами (комментариями) о произведении.
Перейти на страницу:

data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday

  deriving (Eq, Ord, Show, Read, Bounded, Enum)

Так как для нашего типа автоматически сгенерированы экземпляры классов Show и Read, можно конвертировать значения типа в строки и из строк:

ghci> Wednesday

Wednesday

ghci> show Wednesday

"Wednesday"

ghci> read "Saturday" :: Day

Saturday

Поскольку он имеет экземпляры классов Eq и Ord, допускаются сравнение и проверка на равенство:

ghci> Saturday == Sunday

False

ghci> Saturday == Saturday

True

ghci> Saturday > Friday

True

ghci> Monday `compare` Wednesday

LT

Наш тип также имеет экземпляр класса Bounded, так что мы можем найти минимальный и максимальный день.

ghci> minBound :: Day

Monday

ghci> maxBound :: Day

Sunday

Благодаря тому что тип имеет экземпляр класса Enum, можно получать предшествующие и следующие дни, а также задавать диапазоны дней.

ghci> succ Monday

Tuesday

ghci> pred Saturday

Friday

ghci> [Thursday .. Sunday]

[Thursday,Friday,Saturday,Sunday]

ghci> [minBound .. maxBound] :: [Day]

[Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday]

Замечательно!

Синонимы типов

Ранее мы упоминали, что типы [Char] и String являются эквивалентами и могут взаимно заменяться. Это осуществляется с помощью синонимов типов. Синоним типа сам по себе ничего не делает – он просто даёт другое имя существующему типу, облегчая понимание нашего кода и документации. Вот так стандартная библиотека определяет тип String как синоним для [Char]:

type String = [Char]

Ключевое слово type может ввести в заблуждение, потому что на самом деле мы не создаём ничего нового (создаём мы с помощью ключевого слова data), а просто определяем синоним для уже существующего типа.

Если мы создадим функцию, которая преобразует строку в верхний регистр, и назовём её toUpperString, то можем дать ей сигнатуру типа toUpperString :: [Char] –> [Char] или toUpperString :: String –> String. Обе сигнатуры обозначают одно и то же, но вторая легче читается.

Улучшенная телефонная книга

Когда мы работали с модулем Data.Map, то вначале представляли записную книжку в виде ассоциативного списка, а потом преобразовывали его в отображение. Как мы уже знаем, ассоциативный список – это список пар «ключ–значение». Давайте взглянем на этот вариант записной книжки:

phoneBook :: [(String,String)]

phoneBook =

  [("оля","555–29-38")

  ,("женя","452–29-28")

  ,("катя","493–29-28")

  ,("маша","205–29-28")

  ,("надя","939–82-82")

  ,("юля","853–24-92")

  ]

Мы видим, что функция phoneBook имеет тип [(String,String)]. Это говорит о том, что перед нами ассоциативный список, который отображает строки в строки, – но не более. Давайте зададим синоним типа, и мы сможем узнать немного больше по декларации типа:

type PhoneBook = [(String,String)]

Теперь декларация типа для нашей записной книжки может быть такой: phoneBook :: PhoneBook. Зададим также синоним для String.

type PhoneNumber = String

type Name = String

type PhoneBook = [(Name,PhoneNumber)]

Те, кто программирует на языке Haskell, дают синонимы типу String, если хотят сделать объявления более «говорящими» – пояснить, чем являются строки и как они должны использоваться.

Итак, реализуя функцию, которая принимает имя и номер телефона и проверяет, есть ли такая комбинация в нашей записной книжке, мы можем дать ей красивую и понятную декларацию типа:

inPhoneBook :: Name –> PhoneNumber –> PhoneBook –> Bool

inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook

Если бы мы не использовали синонимы типов, тип нашей функции был бы String –> String –> [(String,String)] –> Bool. В этом случае декларацию функции легче понять при помощи синонимов типов. Однако не надо перегибать палку. Мы применяем синонимы типов для того, чтобы описать, как используются существующие типы в наших функциях (таким образом декларации типов лучше документированы), или когда мы имеем дело с длинной декларацией типа, которую приходится часто повторять (вроде [(String,String)]), причём эта декларация обозначает что-то более специфичное в контексте наших функций.

Параметризация синонимов

Синонимы типов также могут быть параметризованы. Если мы хотим задать синоним для ассоциативного списка и при этом нам нужно, чтобы он мог принимать любые типы для ключей и значений, мы можем сделать так:

type AssocList k v = [(k,v)]

Функция, которая получает значение по ключу в ассоциативном списке, может иметь тип (Eq k) => k –> AssocList k v –> Maybe v. Тип AssocList – это конструктор типов, который принимает два типа и производит конкретный тип, например AssocList Int String.

Мы можем частично применять функции, чтобы получить новые функции; аналогичным образом можно частично применять типы-параметры и получать новые конструкторы типов. Так же, как мы вызываем функцию, не передавая всех параметров для того, чтобы получить новую функцию, мы будем вызывать и конструктор типа, не указывая всех параметров, и получать частично применённый конструктор типа. Если мы хотим получить тип для отображений (из модуля Data.Map) с целочисленными ключами, можно сделать так:

type IntMap v = Map Int v

или так:

type IntMap = Map Int

В любом случае конструктор типов IntMap принимает один параметр – это и будет типом, в который мы будем отображать Int.

И вот ещё что. Если вы попытаетесь реализовать этот пример, вам потребуется произвести квалифицированный импорт модуля Data.Map. При квалифицированном импорте перед конструкторами типов также надо ставить имя модуля. Таким образом, мы бы записали: IntMap = Map.Map Int.

Убедитесь, что вы понимаете различие между конструкторами типов и конструкторами данных. Если мы создали синоним типа IntMap или AssocList, это ещё не означает, что можно делать такие вещи, как AssocList [(1,2),(4,5),(7,9)]. Это означает только то, что мы можем ссылаться на тип, используя другое имя. Можно написать: [(1,2),(3,5),(8,9)] :: AssocList Int Int, в результате чего числа в списке будут трактоваться как целые – но мы также сможем работать с этим списком как с обычным списком пар целых чисел. Синонимы типов (и вообще типы) могут использоваться в языке Haskell только при объявлении типов. Часть языка, относящаяся к объявлению типов, – собственно объявление типов (то есть при определении данных и типов) или часть объявления после символа :: (два двоеточия). Символ :: используется при декларировании или аннотировании типов.

Иди налево, потом направо

Ещё один чудесный тип, принимающий два других в качестве параметров, – это тип Either. Он определён приблизительно так:

data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)

У него два конструктора данных. Если используется конструктор Left, его содержимое имеет тип a; если Right – содержимое имеет тип b. Таким образом, мы можем использовать данный тип для инкапсуляции значения одного из двух типов. Когда мы работаем с типом Either a b, то обычно используем сопоставление с образцом по Left и Right и выполняем действия в зависимости от того, какой вариант совпал.

ghci> Right 20

Right 20

ghci> Left "в00т"

Left "в00т"

ghci> :t Right 'a'

Right 'a' :: Either a Char ghci> :t Left True

Left True :: Either Bool b

Из приведённого примера следует, что типом значения Left True является Either Bool b. Первый параметр типа Bool, поскольку значение создано конструктором Left; второй же параметр остался полиморфным. Ситуация подобна тому как значение Nothing имеет тип Maybe a.

Мы видели, что тип Maybe главным образом используется для того, чтобы представить результат вычисления, которое может завершиться неудачей. Но иногда тип Maybe не так удобен, поскольку значение Nothing не несёт никакой информации, кроме того что что-то пошло не так. Это нормально для функций, которые могут выдавать ошибку только в одном случае – или если нам просто не интересно, как и почему функция «упала». Поиск в отображении типа Data.Map может завершиться неудачей, только если искомый ключ не найден, так что мы знаем, что случилось. Но если нам нужно знать, почему не сработала некоторая функция, обычно мы возвращаем результат типа Either a b, где a – это некоторый тип, который может нам что-нибудь рассказать о причине ошибки, и b – результат удачного вычисления. Следовательно, ошибки используют конструктор данных Left, правильные результаты используют конструктор Right.

Перейти на страницу:

Миран Липовача читать все книги автора по порядку

Миран Липовача - все книги автора в одном месте читать по порядку полные версии на сайте онлайн библиотеки kniga-online.club.


Изучай Haskell во имя добра! отзывы

Отзывы читателей о книге Изучай Haskell во имя добра!, автор: Миран Липовача. Читайте комментарии и мнения людей о произведении.


Уважаемые читатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.

  • 1. Просьба отказаться от дискриминационных высказываний. Мы защищаем право наших читателей свободно выражать свою точку зрения. Вместе с тем мы не терпим агрессии. На сайте запрещено оставлять комментарий, который содержит унизительные высказывания или призывы к насилию по отношению к отдельным лицам или группам людей на основании их расы, этнического происхождения, вероисповедания, недееспособности, пола, возраста, статуса ветерана, касты или сексуальной ориентации.
  • 2. Просьба отказаться от оскорблений, угроз и запугиваний.
  • 3. Просьба отказаться от нецензурной лексики.
  • 4. Просьба вести себя максимально корректно как по отношению к авторам, так и по отношению к другим читателям и их комментариям.

Надеемся на Ваше понимание и благоразумие. С уважением, администратор kniga-online.


Прокомментировать
Подтвердите что вы не робот:*
Подтвердите что вы не робот:*