Я пытался сжать стрелки, так как они - основание большинства реализаций FRP. Я думаю, что понимаю основную идею - они связаны с монадами, но хранят статическую информацию в каждом, связывают оператор, таким образом, можно идти через цепочку стрелок и посмотреть на статическую информацию, не имея необходимость оценивать целую стрелку.
Но я заблудился в точке, где мы начинаем обсуждать первый, второй, и подкачка. Что 2 кортежа имеют отношение к стрелкам? Учебные руководства представляют материал кортежа, как будто это был очевидный следующий шаг, но я действительно не вижу соединение.
В этом отношении, что синтаксис стрелки означает интуитивно?
Пожалуйста, загляните в 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
. Мы замечаем, что каррирование в Arrow не работает, потому что разрешена только композиция. Следовательно, мы должны распаковать сначала функцию
uncurry :: (a -> b -> c) -> ((a, b) -> c)
uncurry (+) :: Num a => (a, a) -> a
Затем у нас есть стрелка
(arr.uncurry) (+) :: (Num a, Arrow f) => f (a, a) a
Из-за этого возникает 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 ((a, b), c)
может быть используется вместо этого.)
Редактировать: Из оригинальной статьи Джона Хьюза Обобщение монад на стрелки , в нем указана причина как
4.1 Стрелки и пары
Однако, даже если в случае монад операторы
return
и>> =
- все, что нам нужно, чтобы начать писать полезный код, для стрелок используются аналогичные операторыarr
и>>>
не достаточно. Даже простая монадическая функция сложения, которую мы видели ранееadd :: Monad m => m Int -> m Int -> m Int добавить x y = x >> = \ u -> (y >> = \ v -> return (u + v))
еще не может быть выражен в форме стрелки. Делая зависимость от ввода явной, мы видим, что аналогичное определение должно иметь форму
add :: Arrow a => a b Int -> a b Int -> a b Int добавить f g = ...
, где мы должны последовательно объединить
f
иg
. Единственный доступный оператор последовательности ->>>
, ноf
иg
не имеют правильных типов для компоновки. Действительно, функцияadd
должна сохранять ввод типаb
при вычисленииf
, чтобы иметь возможность предоставлять тот же вход дляg
. Аналогичным образом результатf
должен быть сохранен при вычисленииg
, чтобы два результата в конечном итоге можно было сложить вместе и вернуть.Комбинаторы стрелок, представленные до сих пор, не дают нам возможности сохранить значение в другом вычислении, поэтому у нас нет альтернативы, кроме как ввести другой комбинатор.