Відповіді:
Підйом - це скоріше модель дизайну, ніж математична концепція (хоча я очікую, що хтось тут, зараз, спростує мене, показуючи, як ліфти - це категорія чи щось).
Зазвичай у вас є певний тип даних з параметром. Щось на зразок
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
, і інше, передбачає псевдо-мутацію "контейнера". Також факт, що списки, як правило, повністю зберігаються в пам'яті, тоді як функції зазвичай зберігаються як обчислення. Але запам'ятовування / монорфні списки, які не зберігаються між дзвінками, обидва вибивають з цієї ідеї.