Я не понимаю, что такое «подъем». Должен ли я сначала понять монады, прежде чем понять, что такое «лифт»? (Я тоже совершенно не осведомлен о монадах :) Или кто-то может объяснить мне это простыми словами?
Я не понимаю, что такое «подъем». Должен ли я сначала понять монады, прежде чем понять, что такое «лифт»? (Я тоже совершенно не осведомлен о монадах :) Или кто-то может объяснить мне это простыми словами?
Ответы:
Подъем - это скорее шаблон проектирования, чем математическая концепция (хотя я ожидаю, что кто-то здесь сейчас опровергнет меня, показав, что подъемы - это категория или что-то в этом роде).
Обычно у вас есть некоторый тип данных с параметром. Что-то вроде
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. Это, в свою очередь, может быть применено к следующему аргументу, используя (<*>)
, и так далее. Так что теперь вместо того, чтобы иметь функцию лифта для каждой арности, у вас есть цепочка аппликаций.
lift id == id
и lift (f . g) == (lift f) . (lift g)
.
id
и .
являются стрелкой-идентификатором и композицией стрелки какой-либо категории, соответственно. Обычно , когда речь идет о Haskell, категория в вопросе «Hask», чьи стрелы Haskell функции (другими словами, id
и .
относятся к функциям Haskell вы знаете , и любовь).
instance Functor Foo
, не так instance Foo Functor
ли? Я бы отредактировал себя, но я не уверен на 100%.
Пол и Яирчу - хорошие объяснения.
Я хотел бы добавить, что поднимаемая функция может иметь произвольное количество аргументов и что они не обязательно должны быть одного типа. Например, вы также можете определить liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
В общем случае, снятие функций, которые принимают 1 аргумент, фиксируется в классе типов Functor
, и операция подъема называется fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Обратите внимание на сходство с liftFoo1
типом s. На самом деле, если у вас есть liftFoo1
, вы можете сделать Foo
экземпляр Functor
:
instance Functor Foo where
fmap = liftFoo1
Кроме того, обобщение поднятия на произвольное количество аргументов называется аппликативным стилем . Не стоит углубляться в это, пока вы не поймете отмену функций с фиксированным числом аргументов. Но когда вы это сделаете, в Learn the Haskell есть хорошая глава по этому вопросу. Typeclassopedia еще один хороший документ , который описывает Functor и Аппликативные (а также другие классы типов, прокрутки вниз к правому главе в этом документе).
Надеюсь это поможет!
Давайте начнем с примера (для более ясного представления добавлен пробел):
> 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
. Он преобразует монадическое действие одной монады в действие трансформированной монады.
В общем, «лифт» поднимает функцию / действие в «обернутый» тип (поэтому исходная функция начинает работать «под обертками»).
Лучший способ понять это, монады и т. Д. И понять, почему они полезны, - это, вероятно, написать код и использовать его. Если есть что-то, что вы ранее запрограммировали, что, как вы подозреваете, может извлечь из этого пользу (т.е. это сделает этот код короче и т. Д.), Просто попробуйте это, и вы легко поймете эту концепцию.
Подъем - это концепция, которая позволяет вам преобразовать функцию в соответствующую функцию в другой (обычно более общей) настройке.
взгляните на http://haskell.org/haskellwiki/Lifting
Согласно этому блестящему уроку , функтор - это некоторый контейнер (например Maybe<a>
, List<a>
или Tree<a>
который может хранить элементы какого-то другого типа a
). Я использовал нотацию обобщений Java <a>
для типов элементов a
и представляю элементы как ягоды на дереве Tree<a>
. Есть функция fmap
, которая принимает функцию преобразования элемента a->b
и контейнер functor<a>
. Это относится a->b
к каждому элементу контейнера, эффективно превращающему его в functor<b>
. Когда только первый аргумент подается, a->b
, fmap
ожидает , пока functor<a>
. То есть, a->b
только поставка превращает эту функцию уровня элемента в функцию, functor<a> -> functor<b>
которая работает над контейнерами. Это называется подъемомфункции. Поскольку контейнер также называется функтором , Функторы, а не Монады, являются предпосылкой для подъема. Монады своего рода «параллельны» подъему. Оба полагаются на понятие Функтора и делают f<a> -> f<b>
. Разница в том, что лифт используется a->b
для преобразования, тогда как Monad требует от пользователя определения a -> f<b>
.
r
к типу (давайте использовать c
для разнообразия), являются Функторами. Они не «содержат» ничего c
. В этом случае fmap - это композиция функций, которая принимает a -> b
функцию и r -> a
единицу, чтобы дать вам новую r -> b
функцию. Все еще нет контейнеров. Также, если бы я мог, я бы отметил это снова для последнего предложения.
fmap
это функция, которая ничего не «ждет»; «Контейнер», являющийся Функтором, - вот и вся точка подъема. Кроме того, монады - это, во всяком случае, двойная идея для подъема: монада позволяет вам использовать то, что было поднято несколько раз подряд, как если бы оно было поднято только один раз - это больше известно как выравнивание .
To wait
, to expect
, to anticipate
являются синонимами. Говоря «функция ожидает», я имел в виду «функция ожидает».
b = 5 : a
и f 0 = 55
f n = g n
оба включают псевдо-мутирование «контейнера». Кроме того, тот факт, что списки, как правило, полностью хранятся в памяти, тогда как функции, как правило, хранятся в виде расчета. Но запоминание / монорфные списки, которые не сохраняются между вызовами, разрушают эту идею.