Я не понимаю, что такое «подъем». Должен ли я сначала понять монады, прежде чем понять, что такое «лифт»? (Я тоже совершенно не осведомлен о монадах :) Или кто-то может объяснить мне это простыми словами?
Я не понимаю, что такое «подъем». Должен ли я сначала понять монады, прежде чем понять, что такое «лифт»? (Я тоже совершенно не осведомлен о монадах :) Или кто-то может объяснить мне это простыми словами?
Ответы:
Подъем - это скорее шаблон проектирования, чем математическая концепция (хотя я ожидаю, что кто-то здесь сейчас опровергнет меня, показав, что подъемы - это категория или что-то в этом роде).
Обычно у вас есть некоторый тип данных с параметром. Что-то вроде
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оба включают псевдо-мутирование «контейнера». Кроме того, тот факт, что списки, как правило, полностью хранятся в памяти, тогда как функции, как правило, хранятся в виде расчета. Но запоминание / монорфные списки, которые не сохраняются между вызовами, разрушают эту идею.