Как я могу определить функцию фильтра для стрелок? - PullRequest
3 голосов
/ 19 июля 2011

Я сейчас читаю статью Программирование со стрелками Джона Хьюза и я уже озадачен первым упражнением, в разделе 2.5, на стр. 20.

В нашем распоряжении имеются классы типов Arrow и ArrowChoice, а также экземпляры для функций, потоковых функций [a] -> [b] и монадических функций a -> m b через тип Kleisli.

A mapA пример был приведен:

mapA f = arr listcase >>>
         arr (const []) ||| (f *** mapA >>> arr (uncurry (:)))

Вот попытка:

listcase :: [a] -> (Either () (a,[a]))
listcase [] = Left ()
listcase (x:xs) = Right (x,xs)

helper :: (Bool,a) -> [a] -> Either (a,[a]) [a]
helper (True,x)  y = Left (x,y)
helper (False,x) y = Right y

test :: Arrow a => (b -> Bool) -> a (b,c) ((Bool,b), c)
test p = first (arr p &&& arr id)

filterA :: Arrow a => (b -> Bool) -> a [b] [c]
filterA p = f >>> (g ||| (h >>> (j ||| (filterA p))))
   where f = arr listcase
         g = arr (const [])
         h = test p >>> (uncurry helper)
         j = (arr id *** (filterA p)) >>> (arr (uncurry (:)))

(грубое) обоснование этой тщетной попытки заключается в следующем: С помощью filterA можно сделать два выбора: listcase, как map, и результат применения предиката p. Он начинается как map, проверяя список и возвращая значение Either, используя listcase. В случае пустого списка применяется g, в противном случае все справа от ||| применяется к значению типа (a,[a]), содержащему head и tail соответственно. Сначала применяется функция h, которая применяет предикат, сохраняя head, возвращая значение типа ((Bool, head),tail). Это передается в (uncurry helper), который решает, сохранять ли head или нет в зависимости от значения Bool. Он возвращает результат в виде значения Either, чтобы мы могли применить к нему метод выбора (|||). Это значение передается следующему выбору: (j ||| (filterA p)), так что если предикат содержит True, то j применяется к паре, содержащей head и tail. head фильтруется с помощью id, тогда как filter p применяется к tail. Оба результата возвращаются в паре. Эта пара затем сверяется с использованием arr (uncurry (:)), как map. В противном случае tail передается только filterA p.

Я сомневаюсь, что это так сложно, как я это представляю, я предполагаю, что упускаю что-то совершенно очевидное.

Ответы [ 2 ]

3 голосов
/ 19 июля 2011

Извините, я не совсем следую вашей логике, но давайте посмотрим , что делает код без стрелки . Он

  • Проверяет, является ли список пустым, если да, возвращает
  • В противном случае отключает заголовок списка, вызывает элемент x
  • рекурсив в остальной части списка, вызов результата ys
  • Если предикат p истинен на голове, тогда мы добавляем x к ys.
  • В противном случае мы возвращаем ys

Функция listcase [для реализации первых 2 задач] выглядит неплохо, хотя помните, что вы возвращаете список, поэтому с таким же успехом можете вернуть его вместо unit и переназначить через const [].

У вас есть код рекурсии для третьей пули, похороненный в двух последних случаях, тогда как я выставляю его напрямую, но это нормально.

Для последнего слияния вы пишете это с |||, но, поскольку вам не нужно составлять какие-либо другие стрелки в вашей целевой категории, вы могли бы просто поднять функцию, чтобы выполнить всю работу. В моем коде ниже это rejoin.

filterA :: forall arr a. ArrowChoice arr => arr a Bool -> arr [a] [a]
filterA p = arr lstcase >>> (filterRest ||| id) where
    -- left if has a head, right otherwise
    lstcase :: [a] -> (Either (a, [a]) [a])
    lstcase (x:xs) = Left (x, xs)
    lstcase [] = Right []

    -- if we got a head, then
    --     for the head ("first" in code), map this to itself and p's result on it
    --     recurse on the rest of the list ("second" part)
    --     append the element to the recursion result if p's result is true
    filterRest :: arr (a, [a]) [a]
    filterRest = (first (id &&& p) >>> second (filterA p) >>> arr rejoin)

    rejoin :: ((a, Bool), [a]) -> [a]
    rejoin ((x, True), rest) = x:rest
    rejoin ((x, False), rest) = rest

Конечно, требуется время, чтобы четко выразить свои идеи с помощью ***, &&&, |||, first и т. Д.

Небольшая критика.

  • Убедитесь, что вы реализуете функцию, которую они просят !! Если вы просто возьмете необработанную функцию p, то вы также можете объявить filterA = arr filter. Вы действительно хотите взять поднятую стрелу p. Тем не менее, изменение будет просто набирать p вместо arr p, так что ваш код имеет правильное представление.
  • (uncurry helper) это не что-то в пространстве стрелок, это просто необработанная функция.

При разработке этого материала я обычно пишу скелет и объявляю типы. Это помогает мне понять, что происходит. Например, я начал с

filterA :: ArrowChoice arr => arr a Bool -> arr [a] [a]
filterA p = arr lstcase >>> (filterRest ||| id) where
    -- left if has a head, right otherwise
    lstcase :: [a] -> (Either (a, [a]) [a])
    lstcase = undefined

    filterRest :: arr (a, [a]) [a]
    filterRest = undefined

Однако, когда вы добавляете fa в объявление filterRest, вы должны сказать ему, что arr для filterRest такое же, как для filterA (тип переменной области видимости), поэтому используйте forall arr a. как указано выше.

2 голосов
/ 19 июля 2011

Вот небольшое упрощение:

test :: Arrow a => (b -> Bool) -> a b ([b] -> [b])
test p = arr $ \b -> if p b then (b:) else id
         
filterA :: Arrow a => (b -> Bool) -> a [b] [b]
filterA p = f >>> (g ||| h)
  where f = arr listcase
        g = arr id
        h = (test p *** filterA p) >>> (arr (uncurry ($)))

filterA' :: Arrow a => a b Bool -> a [b] [b]
filterA' p = f >>> (g ||| h)
  where f = arr listcase
        g = arr id
        h = (i *** filterA p) >>> (arr (uncurry ($)))
        i = proc x -> do 
              b <- p -< x
              returnA -< if b then (x:) else id
...