Понимание стрелок в Haskell
Я пытался получить контроль над стрелками, так как они являются основой большинства FRP реализаций. Я думаю, что понимаю основную идею-они связаны с монадами, но хранят статическую информацию в каждом операторе привязки, поэтому вы можете пройти через цепочку стрелок и посмотреть на статическую информацию, не оценивая всю стрелку.
но я теряюсь в точке, где мы начнем обсуждать первый, второй и поменять. Какое отношение 2-кортежи имеют к стрелкам? Учебники представляют материал кортежа, как если бы это был очевидный следующий шаг, но я действительно не вижу связи.
Если на то пошло, что означает синтаксис стрелки интуитивно?
1 ответ:
пожалуйста, загляните в http://www.cs.yale.edu/homes/hudak/CS429F04/AFPLectureNotes.pdf, что объясняет, как стрелки работают в FRP.
2-кортежи используются в определении стрелок, потому что это необходимо для представления стрелочной функции, принимающей 2 аргумента.
в FRP константы и переменные часто представлены в виде стрелок, которые игнорируют его "вход", например
twelve, eleven :: Arrow f => f p Int twelve = arr (const 12) eleven = arr (const 11)функциональные приложения затем превращаются в композиции (
>>>):# (6-) 12 arr (6-) <<< twelveтеперь как мы превращаем функцию с двумя аргументами в стрелку? Например,
(+) :: Num a => a -> a -> aиз-за карринга мы можем рассматривать это как функцию, возвращающую функцию. Так что
arr (+) :: (Arrow f, Num a) => f a (a -> a)теперь давайте применим его к постоянному
arr (+) -- # f a (a -> a) <<< twelve -- # f b Int :: f b (Int -> Int) +----------+ +-----+ +--------------+ | const 12 |----> | (+) | == | const (+ 12) | +----------+ +-----+ +--------------+Эй, подождите, это не сработает. Результатом по-прежнему является стрелка, которая возвращает функцию, но мы ожидаем что-то похожее на
f Int Int. мы замечаем, что карринг терпит неудачу в Стрелке, потому что только состав разрешен. поэтому мы должны uncurry функция сначалаuncurry :: (a -> b -> c) -> ((a, b) -> c) uncurry (+) :: Num a => (a, a) -> aтогда у нас есть стрелка
(arr.uncurry) (+) :: (Num a, Arrow f) => f (a, a) a2-кортеж возникает из-за этого. Тогда куча функций, как
&&&необходимы для решения этих 2-кортежей.(&&&) :: f a b -> f a d -> f a (b, d)после этого добавление можно правильно выполнить.
(arr.uncurry) (+) -- # f (a, a) a <<< twelve -- # f b Int &&& eleven -- # f b Int :: f b a +--------+ |const 12|-----. +--------+ | +-----+ +----------+ &&&====> | (+) | == | const 23 | +--------+ | +-----+ +----------+ |const 11|-----' +--------+(теперь, почему нам не нужны такие вещи, как
&&&&для 3-кортежей для функции 3-х аргументов? Потому что а((a,b),c)можно использовать вместо этого.)
Edit: из оригинальной статьи Джона Хьюза обобщение монад на стрелки, он утверждает причину как
4.1 стрелки и пары
однако, даже если в случае монад операторы
returnи>>=все, что нам нужно, чтобы начать писать полезный код, для стрелок аналогичные операторыarrи>>>недостаточно. Даже простая монадическая функция сложения то, что мы видели раньшеadd :: Monad m => m Int -> m Int -> m Int add x y = x >>= \u -> (y >>= \v -> return (u + v))не может быть выражена в форме стрелки. Делая зависимость от входа явной, мы видим, что аналогичное определение должно принимать вид
add :: Arrow a => a b Int -> a b Int -> a b Int add f g = ...где мы должны объединить
fиgв последовательности. Единственный доступный оператор секвенирования ->>>, аfиgне имеют правильных типов для составления. Действительно, нужно сохранить вход типbпо расчетуf, чтобы иметь возможность поставлять тот же вход вg. Аналогично результатfдолжны быть сохранены через вычислениеg, так что два результата в конечном итоге могут быть добавлены вместе и возвращены. Введенные до сих пор комбинаторы стрелок не дают нам возможности сохранить значение в другом вычислении, и поэтому у нас нет альтернативы, кроме как ввести другой комбинатор.
Comments