Пожалуйста, посмотрите в 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-кортеж. Тогда для работы с этими 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|-----'
+--------+
(Теперь, почему нам не нужны такие вещи, как &&&&
для трех кортежей для функций с тремя аргументами? Потому что вместо этого можно использовать ((a,b),c)
.)
Редактировать: Из оригинальной статьи Джона Хьюза Обобщая Монады в Стрелы , в ней указывается причина
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
не имеют правильных типов для составления. Действительно, функция add
должна сохранить входные данные типа b
в расчете на f
, чтобы иметь возможность предоставить тот же вход для g
. Аналогично, результат f
должен быть сохранен во время вычисления g
, чтобы два результата в итоге можно было сложить и вернуть. Комбинации стрелок, представленные ранее, не дают нам способа сохранить значение в другом вычислении, и поэтому у нас нет другого выбора, кроме как ввести другой комбинатор.