Что “поднимается” в Haskell?

Я не понимаю, каков "подъем". Я должен сначала понять монады прежде, чем понять, каков "лифт"? (Я абсолютно не осведомлен о монадах, также :) Или кто-то может объяснить это мне с простыми словами?

129
задан APerson 10 February 2018 в 22:43
поделиться

4 ответа

Подъем - это скорее шаблон проектирования, чем математическая концепция (хотя я ожидаю, что кто-нибудь здесь сейчас опровергнет меня, показав, что подъем - это категория или что-то в этом роде).

Обычно у вас есть некоторый тип данных с параметром. Что-то вроде

data Foo a = Foo { ...stuff here ...}

Предположим, вы обнаружили, что во многих случаях в Foo используются числовые типы (Int, Double и т.д.), и вам приходится писать код, который разворачивает эти числа, складывает или умножает их, а затем заворачивает обратно. Вы можете сократить этот процесс, написав код разворачивания и сворачивания один раз. Эта функция традиционно называется "лифтом", потому что она выглядит так:

liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c

Другими словами, у вас есть функция, которая берет функцию с двумя аргументами (например, оператор (+)) и превращает ее в эквивалентную функцию для Foos.

Так что теперь вы можете написать

addFoo = liftFoo2 (+)

Редактировать: дополнительная информация

Конечно, вы можете иметь liftFoo3, liftFoo4 и так далее. Однако часто в этом нет необходимости.

Начните с наблюдения

liftFoo1 :: (a -> b) -> Foo a -> Foo b

Но это точно так же, как fmap. Поэтому вместо liftFoo1 можно написать

instance Functor Foo where
   fmap f foo = ...

Если вы действительно хотите полной регулярности, то можно сказать

liftFoo1 = fmap

Если вы можете превратить Foo в вектор, то, возможно, вы можете сделать его аппликативным вектором. На самом деле, если вы можете написать liftFoo2, то аппликативный экземпляр выглядит так:

import Control.Applicative

instance Applicative Foo where
   pure x = Foo $ ...   -- Wrap 'x' inside a Foo.
   (<*>) = liftFoo2 ($)

Оператор (<*>) для Foo имеет тип

(<*>) :: Foo (a -> b) -> Foo a -> Foo b

Он применяет обернутую функцию к обернутому значению. Так что если вы можете реализовать liftFoo2, то вы можете написать это в терминах этого оператора. Или вы можете реализовать его напрямую и не заморачиваться с liftFoo2, потому что модуль Control.Applicative включает

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c

и аналогично есть liftA и liftA3. Но на самом деле вы не часто их используете, потому что есть еще один оператор

(<$>) = fmap

Он позволяет вам написать:

result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4

Термин myFunction <$> arg1 возвращает новую функцию, обернутую в Foo. Она, в свою очередь, может быть применена к следующему аргументу с помощью (<*>), и так далее. Таким образом, теперь вместо того, чтобы иметь функцию подъема для каждой аритичности, у вас есть просто гирляндная цепочка аппликаций.

173
ответ дан 24 November 2019 в 00:31
поделиться

Начнем с примера (для более ясного представления добавлено немного белого пространства):

> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate        ::         Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) =>       f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"

liftA2 преобразует функцию простых типов в функцию тех же типов, обернутую в Applicative, таких как списки, IO и т.д.

Другим распространенным лифтом является lift из Control.Monad.Trans. Он преобразует монадическое действие одной монады в действие преобразованной монады.

В общем случае "lift" поднимает функцию/действие в "обернутый" тип (так что исходная функция работает "под обертыванием").

Лучший способ понять это, а также монады и т.д., и понять, почему они полезны, - это, вероятно, написать код и использовать его. Если есть что-то, что вы написали ранее, что, как вы подозреваете, может выиграть от этого (т.е. это сделает код короче и т.д.), просто попробуйте это, и вы легко поймете концепцию.

24
ответ дан 24 November 2019 в 00:31
поделиться

Lifting - это концепция, которая позволяет преобразовать функцию в соответствующую функцию в другой (обычно более общей) настройке

посмотрите на http://haskell.org/haskellwiki/Lifting

14
ответ дан 24 November 2019 в 00:31
поделиться

Оба примера Пола и Яирчу являются хорошими объяснениями.

Я хотел бы добавить, что функция является lifted может иметь произвольное количество аргументов, и они не обязательно должны быть одного и того же типа. Например, вы также можете определить liftFoo1:

liftFoo1 :: (a -> b) -> Foo a -> Foo b

Как правило, подъем функций, которые принимают 1 аргумент, фиксируется в type class Functor , а операция подъема называется fmap :

fmap :: Functor f => (a -> b) -> f a -> f b

Обратите внимание на сходство с типом liftFoo1 . Фактически, если у вас есть ] liftFoo1 , вы можете сделать Foo экземпляром Functor :

instance Functor Foo where
  fmap = liftFoo1

Кроме того, обобщение подъема до произвольного числа аргументов называется аппликативным стилем . Не погружайтесь в это, пока не поймете подъем функций с фиксированным числом аргументов. Но когда вы это сделаете, в Learn you a Haskell есть хорошая глава по этому поводу. Typeclassopedia - еще один хороший документ, который описывает Functor и Applicative (а также другие классы типов; прокрутите вниз до правой главы в этом документе).

Надеюсь, это поможет!

40
ответ дан 24 November 2019 в 00:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: