能否使用ghc箭头符号重写这个示例?

4
我重新发明了某种“状态箭头”:
import Prelude hiding (id, (.))
import Control.Monad.State
import Control.Arrow
import Control.Category

data StateA s a b = StateA {runStateA :: s -> a -> (b, s)}

instance Category (StateA s) where
  id = StateA (\s a -> (a, s))

  (StateA f) . (StateA g) = StateA $ \s x -> let (b, s') = g s x in f s' b

instance Arrow (StateA s) where
  arr f = StateA $ \s a -> (f a, s)

  first (StateA f) = StateA $ \s (b, d) -> let (c, s') = f s b in ((c, d), s)

put' :: s -> StateA s b ()
put' s = StateA $ \_ _ -> ((), s)

get' :: StateA s b s
get' = StateA $ \s _ -> (s, s)

merge :: (s -> s -> s) -> StateA s a b -> StateA s a c -> StateA s a (b, c)
merge f (StateA a) (StateA b) = StateA $ \s x ->
  let (ra, sa) = a s x
      (rb, sb) = b s x 
  in ((ra, rb), f sa sb)


 test = (flip runStateA) s bar 
   where bar = ((put' 7) >>> get') &&& get'

看起来这个定义符合我的期望:至少测试3 5会得到以下结果:

((7,3), 3)

请注意,这种行为有意不同于普通状态单子包装成如下箭头形式的单子:
liftKC = Kleisli . const

putM :: a -> Kleisli (State a) b ()
putM = liftKC . put

getM :: Kleisli (State a) b a
getM = liftKC get

foo :: (Num a) => Kleisli (State a) a (a, a)
foo = (putM 7 >>> getM) &&& getM

testKleisli a b = (flip runState) a $
                  (flip runKleisli) b foo

当执行testKleisli 3 5时,返回结果为

((7, 7), 7).

重点是可以将状态在一些“并行计算的分支”中分别进行操作,然后以某种方式合并它。

我不熟悉箭头符号,但在这里使用它是不方便的:它看起来像是为每个计算创建新的“分支”的语法糖。是否可能使用箭头符号重写'test'的where子句中的“bar”函数?


1
难道不应该是 first (StateA f) = StateA $ \s (b, d) -> let (c, s') = f s b in ((c, d), s')(注意末尾的s',而不是普通的s)吗? - Sassa NF
@SassaNF 不应该这样。否则它会像后面的例子一样表现。 - user3974391
@SassaNF 我其实可以使用修改后的状态,但之后它会被遗忘。试着手动扩展 (a >>> b) &&& c。 - user3974391
@leftaroundabout 我到目前为止没有使用过Writer模拟器,但是它的状态似乎是Monoid。所以我不认为它与我的例子有任何关系。也许你是指Reader模拟器? - user3974391
@user2894391:只有WriterMonad实例需要Monoid,但由于这些只是函数,在两侧都有一个writer(而不是Kleisli箭头),因此基本上所有内容都在Functor级别上完成,其中不需要Monoid - leftaroundabout
显示剩余5条评论
1个回答

11
让我们画一幅关于IT技术的图画。
bar = ((put' 7) >>> get') &&& get'

为了给我们一个写箭头符号表示的想法,请看下图。就像单子(do)符号一样,proc符号引入了命名变量,用显式传递值替代了组合器(如>>=)。无论如何,我们可以看到我们需要将输入x提供给两边,即:
bar' = proc x -> do
        wasput <- put' 7 >>> get' -< x
        justgot <- get' -< x
        returnA -< (wasput,justgot)

或者,如果我们希望一切都从右到左进行,同样地,
bar'' = proc x -> do
        wasput <- get' <<< put' 7 -< x
        justgot <- get' -< x
        returnA -< (wasput,justgot)

测试

我将会重构测试以进行多次测试:

test s b = (flip runStateA) s b

所以我们得到:
ghci> test bar 3 5
((7,3),3)
ghci> test bar' 3 5
((7,3),3)
ghci> test bar'' 3 5
((7,3),3)

我们能否不使用>>>来编写代码?

我们可能会尝试将(>>>)分解出来:

bar''' = proc x -> do
        put7 <- put' 7 -< x
        wasput <- get' -< put7
        justgot <- get' -< x
        returnA -< (wasput,justgot)

抱歉,没有找到:

ghci> test bar''' 3 5
((3,3),3)

正如你所指出的,你的状态是局部的,而且“put' 7”并没有穿过到“get'”,所以我们无法摆脱“>>>”或“<<<”组合器。
我不禁感觉这有点违反了 Arrow 法则。嗯……
破损的 Arrow 法则
我花了一段时间进行手动展开和图表分析,最终找到了一条明显违反你实例的 Arrow 法则:
first (f >>> g) = first f >>> first g

如果我们定义
dup :: Arrow a => a t (t, t)
dup = arr (\x -> (x,x))    

我们获得

ghci> test (dup >>> (first (put' 7    >>>     get'))) 1 3
((7,3),1)
ghci> test (dup >>> (first (put' 7) >>> first get')) 1 3
((1,3),1)

这是因为第二个例子中put' 7的本地状态没有传递到第二个first,如果你能跟上所有这些“first”和“second”的话!
结论:
你发现箭头符号对于你的箭头实例来说不太有用,因为它假设可以通过不成立的法律进行转换。
可悲的是,尽管非常有趣,非常分散注意力,但它不是一个真正的箭头。

非常详尽的答案,谢谢。但如果它不是箭头(我没有先查看规定,真丢脸),那么它可能是什么?我喜欢它的行为。 - user3974391

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接