`
Читать книги » Книги » Компьютеры и Интернет » Программирование » Миран Липовача - Изучай Haskell во имя добра!

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

1 ... 61 62 63 64 65 ... 96 ВПЕРЕД
Перейти на страницу:

Выражение (++) <$> getLine <*> getLine имеет тип IO String. Это означает, что данное выражение является совершенно обычным действием ввода-вывода, как и любое другое, тоже возвращая результирующее значение, подобно другим действиям ввода-вывода. Вот почему мы можем выполнять следующие вещи:

main = do

   a <– (++) <$> getLine <*> getLine

   putStrLn $ "Две строки, соединённые вместе: " ++ a

Функции в качестве аппликативных функторов

Ещё одним экземпляром класса Applicative является тип (–>) r, или функции. Мы нечасто используем функции в аппликативном стиле, но концепция, тем не менее, действительно интересна, поэтому давайте взглянем, как реализован экземпляр функции[12].

instance Applicative ((–>) r) where

   pure x = (_ –> x)

   f <*> g = x –> f x (g x)

Когда мы оборачиваем значение в аппликативное значение с помощью функции pure, результат, который оно возвращает, должен быть этим значением. Минимальный контекст по умолчанию по-прежнему возвращает это значение в качестве результата. Вот почему в реализации экземпляра функция pure принимает значение и создаёт функцию, которая игнорирует передаваемый ей параметр и всегда возвращает это значение. Тип функции pure для экземпляра типа (–>) r выглядит как pure :: a –> (r –> a).

ghci> (pure 3) "ля"

3

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

ghci> pure 3 "ля"

3

Реализация экземпляра <*> немного загадочна, поэтому давайте посмотрим, как использовать функции в качестве аппликативных функторов в аппликативном стиле:

ghci> :t (+) <$> (+3) <*> (*100)

(+) <$> (+3) <*> (*100) :: (Num a) => a –> a

ghci> (+) <$> (+3) <*> (*100) $ 5

508

Вызов оператора <*> с двумя аппликативными значениями возвращает аппликативное значение, поэтому если мы вызываем его с двумя функциями, то получаем функцию. Что же здесь происходит? Когда мы выполняем (+) <$> (+3) <*> (*100), мы создаём функцию, которая применит оператор + к результатам выполнения функций (+3) и (*100) и вернёт это значение. При вызове выражения (+) <$> (+3) <*> (*100) $ 5 функции (+3) и (*100) сначала применяются к значению 5, что в результате даёт 8 и 500; затем оператор + вызывается со значениями 8 и 500, что в результате даёт 508.

Следующий код аналогичен:

ghci> (x y z –> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5

[8.0,10.0,2.5]

Мы создаём функцию, которая вызовет функцию x y z –> [x, y, z] с окончательными результатами выполнения, возвращёнными функциями (+3), (*2) и (/2). Значение 5 передаётся каждой из трёх функций, а затем с этими результатами вызывается анонимная функция x y z –> [x, y, z].

ПРИМЕЧАНИЕ. Не так уж важно, поняли ли вы, как работает экземпляр типа (–>) r для класса Applicative, так что не отчаивайтесь, если вам это пока не ясно. Поработайте с аппликативным стилем и функциями, чтобы получить некоторое представление о том, как использовать функции в качестве аппликативных функторов.

Застёгиваемые списки

Оказывается, есть и другие способы для списков быть аппликативными функторами. Один способ мы уже рассмотрели: вызов оператора <*> со списком функций и списком значений, который возвращает список всех возможных комбинаций применения функций из левого списка к значениям в списке справа.

Например, если мы выполним [(+3),(*2)] <*> [1,2], то функция (+3) будет применена и к 1, и к 2; функция (*2) также будет применена и к 1, и к 2, а результатом станет список из четырёх элементов: [4,5,2,4]. Однако [(+3),(*2)] <*> [1,2] могла бы работать и таким образом, чтобы первая функция в списке слева была применена к первому значению в списке справа, вторая была бы применена ко второму значению и т. д. Это вернуло бы список с двумя значениями: [4,4]. Вы могли бы представить его как [1 + 3, 2 * 2].

Экземпляром класса Applicative, с которым мы ещё не встречались, является тип ZipList, и находится он в модуле Control.Applicative.

Поскольку один тип не может иметь два экземпляра для одного и того же класса типов, был введён тип ZipList a, в котором имеется один конструктор (ZipList) с единственным полем (список). Вот так определяется его экземпляр:

instance Applicative ZipList where

   pure x = ZipList (repeat x)

   ZipList fs <*> ZipList xs = ZipList (zipWith (f x –> f x) fs xs)

Оператор <*> применяет первую функцию к первому значению, вторую функцию – ко второму значению, и т. д. Это делается с помощью выражения zipWith (f x –> f x) fs xs. Ввиду особенностей работы функции zipWith окончательный список будет той же длины, что и более короткий список из двух.

Функция pure здесь также интересна. Она берёт значение и помещает его в список, в котором это значение просто повторяется бесконечно. Выражение pure "ха-ха" вернёт ZipList (["ха-ха","ха-ха","ха-ха"… Это могло бы сбить с толку, поскольку вы узнали, что функция pure должна помещать значение в минимальный контекст, который по-прежнему возвращает данное значение. И вы могли бы подумать, что бесконечный список чего-либо едва ли является минимальным. Но это имеет смысл при использовании застёгиваемых списков, так как значение должно производиться в каждой позиции. Это также удовлетворяет закону о том, что выражение pure f <*> xs должно быть эквивалентно выражению fmap f xs. Если бы вызов выражения pure 3 просто вернул ZipList [3], вызов pure (*2) <*> ZipList [1,5,10] дал бы в результате ZipList [2], потому что длина результирующего списка из двух застёгнутых списков равна длине более короткого списка из двух. Если мы застегнём конечный список с бесконечным, длина результирующего списка всегда будет равна длине конечного списка.

Так как же застёгиваемые списки работают в аппликативном стиле? Давайте посмотрим.

Ладно, тип ZipList a не имеет экземпляра класса Show, поэтому мы должны использовать функцию getZipList для извлечения обычного списка из застёгиваемого:

ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]

[101,102,103]

ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..]

[101,102,103]

ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]

[5,3,3,4]

ghci> getZipList $ (,,) <$> ZipList "пар" <*> ZipList "ток" <*> ZipList "вид"

[('п','т','в'),('а','о','и'),('р',кt','д')]

ПРИМЕЧАНИЕ. Функция (,,) – это то же самое, что и анонимная функция x y z –> (x,y,z). В свою очередь, функция (,) – то же самое, что и x y –> (x,y).

Помимо функции zipWith в стандартной библиотеке есть такие функции, как zipWith3, zipWith4, вплоть до 7. Функция zipWith берёт функцию, которая принимает два параметра, и застёгивает с её помощью два списка. Функция zipWith3 берёт функцию, которая принимает три параметра, и застёгивает с её помощью три списка, и т. д. При использовании застёгиваемых списков в аппликативном стиле нам не нужно иметь отдельную функцию застёгивания для каждого числа списков, которые мы хотим застегнуть друг с другом. Мы просто используем аппликативный стиль для застёгивания произвольного количества списков при помощи функции, и это очень удобно.

Аппликативные законы

Как и в отношении обычных функторов, применительно к аппликативным функторам действует несколько законов. Самый главный состоит в том, чтобы выполнялось тождество pure f <*> x = fmap f x. В качестве упражнения можете доказать выполнение этого закона для некоторых аппликативных функторов из этой главы. Ниже перечислены другие аппликативные законы:

• pure id <*> v = v

• pure (.) <*> u <*> v <*> w = u <*> (v <*> w)

• pure f <*> pure x = pure (f x)

• u <*> pure y = pure ($ y) <*> u

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

Полезные функции для работы с аппликативными функторами

Модуль Control.Applicative определяет функцию, которая называется liftA2 и имеет следующий тип:

1 ... 61 62 63 64 65 ... 96 ВПЕРЕД
Перейти на страницу:

Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Миран Липовача - Изучай Haskell во имя добра!, относящееся к жанру Программирование. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.

Комментарии (0)