Понимание стрел в Хаскелле

Я пытался захватить стрелки, поскольку они являются основой большинства 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

Теперь как мы превратим функцию 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, так что эти два результата могут быть добавлены вместе и возвращены. Комбинированные стрелки, представленные до сих пор, не дают нам возможности сохранить значение в другом вычислении, поэтому у нас нет альтернативы, кроме как ввести другой комбинатор.

Ответ 2

Какие стрелки? Вы имеете в виду стрелки, как в (\x -> func x), или как в [ x*x | x <- [1..10]]?

Первое - приложение lamba: точка, которая следует за объявлением символа в заявлении лямбда; и если вы разворачиваете семантику, она в основном преобразует let x = some_value in func x в собственную функцию: определите функцию, вызывающую func в ее операнде. Вторая аналогична теории множеств: возьмем х из набора натуральных чисел в диапазоне от 1 до 10 (включительно).