Я не понимаю, каков "подъем". Я должен сначала понять монады прежде, чем понять, каков "лифт"? (Я абсолютно не осведомлен о монадах, также :) Или кто-то может объяснить это мне с простыми словами?
Подъем - это скорее шаблон проектирования, чем математическая концепция (хотя я ожидаю, что кто-нибудь здесь сейчас опровергнет меня, показав, что подъем - это категория или что-то в этом роде).
Обычно у вас есть некоторый тип данных с параметром. Что-то вроде
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. Она, в свою очередь, может быть применена к следующему аргументу с помощью (<*>)
, и так далее. Таким образом, теперь вместо того, чтобы иметь функцию подъема для каждой аритичности, у вас есть просто гирляндная цепочка аппликаций.
Начнем с примера (для более ясного представления добавлено немного белого пространства):
> 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" поднимает функцию/действие в "обернутый" тип (так что исходная функция работает "под обертыванием").
Лучший способ понять это, а также монады и т.д., и понять, почему они полезны, - это, вероятно, написать код и использовать его. Если есть что-то, что вы написали ранее, что, как вы подозреваете, может выиграть от этого (т.е. это сделает код короче и т.д.), просто попробуйте это, и вы легко поймете концепцию.
Lifting - это концепция, которая позволяет преобразовать функцию в соответствующую функцию в другой (обычно более общей) настройке
посмотрите на http://haskell.org/haskellwiki/Lifting
Оба примера Пола и Яирчу являются хорошими объяснениями.
Я хотел бы добавить, что функция является 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 (а также другие классы типов; прокрутите вниз до правой главы в этом документе).
Надеюсь, это поможет!