Понимание стрелок в 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) a
2-кортеж возникает из-за этого. Тогда куча функций, как
&&&
необходимы для решения этих 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