Відповіді:
Підйом - це скоріше модель дизайну, ніж математична концепція (хоча я очікую, що хтось тут, зараз, спростує мене, показуючи, як ліфти - це категорія чи щось).
Зазвичай у вас є певний тип даних з параметром. Щось на зразок
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 you a 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>яка працює над контейнерами. Це називається підйомомфункції. Оскільки контейнер також називають функтором , передумовою для підйому є саме Функтори, а не Монади. Монади є свого роду "паралельними" підняттям. Обидва покладаються на поняття Functor і роблять f<a> -> f<b>. Різниця полягає в тому, що ліфтинг використовує a->bдля конверсії, тоді як Monad вимагає від користувача визначення a -> f<b>.
rдо типу (давайте використовувати cдля різноманітності) - це Functors. Вони не "містять" жодних 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, і інше, передбачає псевдо-мутацію "контейнера". Також факт, що списки, як правило, повністю зберігаються в пам'яті, тоді як функції зазвичай зберігаються як обчислення. Але запам'ятовування / монорфні списки, які не зберігаються між дзвінками, обидва вибивають з цієї ідеї.