Подтвердить что ты не робот

Хаскелл: Я не понимаю, как можно использовать стрелы?

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

{-# LANGUAGE Arrows#-}
module StatefulFunc where

import Control.Category
import Control.Arrow

newtype StatefulFunc a b = SF { unSF :: a -> (StatefulFunc a b, b) }

idSF :: StatefulFunc a a
idSF = SF $ \a -> (idSF, a)

dotSF :: StatefulFunc b c -> StatefulFunc a b -> StatefulFunc a c
dotSF f g = SF $ \a -> 
    let (g', b) = unSF g a
        (f', c) = unSF f b
    in (dotSF f' g', c)

instance Category StatefulFunc where
  id = idSF
  (.) = dotSF

arrSF :: (a -> b) -> StatefulFunc a b
arrSF f = ret
  where ret = SF fun
        fun a = (ret, f a)

bothSF :: StatefulFunc a b -> StatefulFunc a' b' -> StatefulFunc (a, a') (b, b')
bothSF f g = SF $ \(a,a') ->
    let (f', b) = unSF f a
        (g', b') = unSF g a'
    in (bothSF f' g', (b, b'))

splitSF :: StatefulFunc a b -> StatefulFunc a b' -> StatefulFunc a (b, b')
splitSF f g = SF $ \a ->
    let (f', b) = unSF f a
        (g', b') = unSF g a
    in (splitSF f' g', (b, b'))

instance Arrow StatefulFunc where
  arr  = arrSF
  first = flip bothSF idSF
  second = bothSF idSF
  (***) = bothSF
  (&&&) = splitSF

eitherSF :: StatefulFunc a b -> StatefulFunc a' b' -> StatefulFunc (Either a a') (Either b b')
eitherSF f g = SF $ \e -> case e of
      Left a -> let (f', b) = unSF f a in (eitherSF f' g, Left b)
      Right a' -> let (g', b') = unSF g a' in (eitherSF f g', Right b')

mergeSF :: StatefulFunc a b -> StatefulFunc a' b -> StatefulFunc (Either a a') b
mergeSF f g = SF $ \e -> case e of 
      Left a -> let (f', b) = unSF f a in (mergeSF f' g, b)
      Right a' -> let (g', b) = unSF g a' in (mergeSF f g', b)

instance ArrowChoice StatefulFunc where
  left = flip eitherSF idSF
  right = eitherSF idSF
  (+++) = eitherSF
  (|||) = mergeSF

Итак, после того, как я просмотрел различные определения классов типов (не уверен, что или как ArrowZero будет работать для этого, поэтому я пропустил его), я определил некоторые вспомогательные функции

evalSF :: (StatefulFunc a b) -> a -> b
evalSF f a = snd (unSF f a)

givenState :: s -> (s -> a -> (s, b)) -> StatefulFunc a b
givenState s f = SF $ \a -> let (s', b) = f s a in (givenState s' f, b)

И разработал пример использования

count :: StatefulFunc a Integer
count = givenState 1 $ \c _ -> (c+1, c)

countExample :: StatefulFunc a Integer
countExample = proc _ -> do
                  (count', one) <- count -< ()
                  (count'', two) <- count' -< ()
                  (count''', three) <- count'' -< ()
                  returnA -< three

Однако, когда я пытаюсь скомпилировать countExample, я получаю ошибки "Не в масштабе" для count' и count'', что, я полагаю, означает, что мне нужно вернуться к учебнику и прочитать, что может будет использоваться, когда. Я думаю, что мне все равно понравится

countExample :: Integer
countExample =
  let (count', one) = unSF count ()
      (count'', two) = unSF count' ()
      (count''', three) = unSF count'' ()
  in three

Но это было неловко, и я надеялся на что-то более естественное.

Может ли кто-нибудь объяснить, как я не понимаю, как работают стрелки, и как они могут быть использованы? Есть ли фундаментальная философия для стрел, которые мне не хватает?

4b9b3361

Ответ 1

Может ли кто-нибудь объяснить, как я не понимаю, как работают стрелки, и как они могут быть использованы? Есть ли фундаментальная философия для стрел, которые мне не хватает?

Мне кажется, что вы относитесь к этому Arrow, как к Monad. Я не знаю, считается ли это "фундаментальной философией", но между ними существует значительная разница, несмотря на то, как часто они пересекаются. В известном смысле ключевым элементом, определяющим a Monad, является функция join; как свернуть вложенную структуру в один слой. Они полезны из-за того, что позволяет join: вы можете создавать новые монадические слои в рекурсивной функции, изменять структуру Functor на основе ее содержимого и т.д. Но это не о Monad s, поэтому мы оставим это на этом.

Суть Arrow, с другой стороны, это обобщенная версия функции. Класс типа Category определяет обобщенные версии состава функций и функции идентификации, а класс типа Arrow определяет, как поднять регулярную функцию до Arrow и как работать с Arrow, которые принимают несколько аргументов (в форма кортежей - Arrows не обязательно должна быть сделана!).

При объединении Arrow базовым способом, как и в вашей первой функции countExample, все, что вы действительно делаете, это нечто вроде сложной композиции функций. Оглянитесь на свое определение (.) - вы берете две функции с состоянием и соединяете их в одну функцию с состоянием, при этом поведение изменения состояния обрабатывается автоматически.

Итак, главная проблема с вашим countExample заключается в том, что он даже упоминает count' и тому подобное. Это все сделано за кулисами, так же как вам не нужно явно передавать параметр состояния при использовании обозначения do в монаде State.

Теперь, поскольку нотация proc просто позволяет вам построить большой составной Arrow s, чтобы фактически использовать вашу функцию состояния, вам нужно будет работать вне синтаксиса Arrow, как вам нужно runState или такой чтобы фактически выполнить вычисление в монаде State. Ваш второй countExample находится вдоль этих строк, но слишком специализирован. В общем случае ваша функция состояния отображает поток входов в поток выходов, делая его конечным преобразователем состояния, поэтому runStatefulFunction будет вероятно, возьмут ленивый список входных значений и преобразуют их в ленивый список выходных значений, используя правую скрость с помощью unSF, чтобы поочередно подавать их на преобразователь.

Если вы хотите увидеть пример, пакет Arrows включает трансформатор Arrow Automaton, который определяет что-то почти идентичный вашему StatefulFunction, за исключением произвольного Arrow вместо простой функции, которую вы использовали.


Oh и кратко пересмотреть связь между Arrow и Monad s:

Plain Arrows - это только функции "первого порядка" . Как я уже говорил, они не всегда могут быть карри, и они не всегда могут "применяться" в том же смысле, что функция ($) применяет функции. Если вы действительно хотите более высокого порядка Arrows, класс типа ArrowApply определяет приложение Arrow. Это добавляет большую силу для Arrow и, среди прочего, позволяет использовать ту же функцию "свернуть вложенную структуру" , что Monad, что позволяет в целом определить экземпляр Monad для любого экземпляра ArrowApply.

В другом направлении, поскольку Monad позволяет комбинировать функции, которые создают новую монадическую структуру, для любого Monad m вы можете говорить о "стрелке Клейсли" , которая является функцией типа a -> m b. Стрелкам Kleisli для a Monad может быть задан экземпляр Arrow довольно очевидным образом.

Помимо ArrowApply и стрелок Kleisli, нет особо интересных отношений между типами классов.