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


Я пытался получить контроль над стрелками, так как они являются основой большинства FRP реализаций. Я думаю, что понимаю основную идею-они связаны с монадами, но хранят статическую информацию в каждом операторе привязки, поэтому вы можете пройти через цепочку стрелок и посмотреть на статическую информацию, не оценивая всю стрелку.

но я теряюсь в точке, где мы начнем обсуждать первый, второй и поменять. Какое отношение 2-кортежи имеют к стрелкам? Учебники представляют материал кортежа, как если бы это был очевидный следующий шаг, но я действительно не вижу связи.

Если на то пошло, что означает синтаксис стрелки интуитивно?

1   51   2010-07-01 06:08:56

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