Что такое "подъем" в Haskell?



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

559   5  

5 ответов:

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

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

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

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

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

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

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

addFoo = liftFoo2 (+)

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

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

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

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

но это точно так же, как fmap. Так что вместо liftFoo1 вы пишите

instance Functor Foo where
   fmap foo = ...

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

liftFoo1 = fmap

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

import Control.Applicative

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

The (<*>) оператор для 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. Это, в свою очередь, может быть применено к следующему аргументу с помощью (<*>) и так далее. Так что теперь вместо того, чтобы иметь функцию подъема для каждого arity, у вас просто есть цепочка приложений.

Павел и яирчу оба хорошие объяснения.

Я хотел бы добавить, что поднимаемая функция может иметь произвольное количество аргументов и что они не должны быть одного типа. Например, вы также можете определить 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

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

надеюсь, что это помогает!

давайте начнем с примера:

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

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

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

вообще, подъемы " поднимают "функцию/действие в" обернутый " тип.

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

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

взгляните на http://haskell.org/haskellwiki/Lifting

по данным этот блестящий учебник, функтор-это некоторый контейнер (например,Maybe<a>,List<a> или Tree<a> который может хранить элементы какого-то другого типа, a). Я использовал нотацию Java generics,<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 для преобразования в то время как монада требует от пользователя, чтобы определить a -> f<b>.

Comments

    Ничего не найдено.