Ответ 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
Теперь как мы превратим функцию 2-аргумента в стрелку? Например,
(+) :: 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
. Мы замечаем, что currying терпит неудачу в 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|-----'
+--------+
(Теперь почему нам не нужны такие вещи, как &&&&
для 3-кортежей для функций, имеющих 3 аргумента? Потому что вместо ((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
, так что эти два результата могут быть добавлены вместе и возвращены. Комбинированные стрелки, представленные до сих пор, не дают нам возможности сохранить значение в другом вычислении, поэтому у нас нет альтернативы, кроме как ввести другой комбинатор.