haskell – 与Netwire一起使用时误解ArrowLoop


this post的优秀答案之后,我试图找到一个不使用箭头符号的ArrowLoop的工作示例.在我完全理解箭头如何在引擎盖下工作之前,我对使用箭头符号感到不舒服.话虽这么说,我已经构建了一个小程序,基于我对Arrows的(有限的)理解应该工作.然而,它最终以可怕的<< >终止.例外:

module Main where

import Control.Wire
import FRP.Netwire

farr :: SimpleWire (Int, Float) (String, Float)
farr = let
  fn :: Int -> Float -> ((String, Float), SimpleWire (Int, Float) (String, Float))
  fn i f = (("f+i: " ++ (show (fromIntegral i + f)), f + 0.1), loopFn)

  loopFn :: SimpleWire (Int, Float) (String, Float)
  loopFn = mkSFN $\(i, f) -> fn i f
  in
   mkSFN $\(i, _) -> fn i 0.0

main :: IO ()
main = do
  let sess = clockSession_ :: Session IO (Timed NominalDiffTime ())
  (ts, sess2) <- stepSession sess

  let wire = loop farr
      (Right s, wire2) = runIdentity $stepWire wire ts (Right 0)

  putStrLn ("s: " ++ s)

  (ts2, _) <- stepSession sess2
  let (Right s2, _) = runIdentity $stepWire wire2 ts (Right 1)

  putStrLn ("s2: " ++ s2)

我的直觉告诉我<< loop>>异常通常在您不向循环提供初始值时出现.我不是用包含fn i 0.0的行完成的吗?输出不同意:

$./test
s: f+i: 0.0
test.exe: <<loop>>

有谁知道我做错了什么?

最佳答案 混淆的主要点似乎是ArrowLoop和mfix之间的整体关系.对于初学者来说,修复是一个函数,即
finds the fixed point的给定函数:

fix :: (a -> a) -> a
fix f = let x = f x in x

mfix是这个函数的monadic扩展,其类型签名不出所料:

mfix :: (a -> m a) -> m a

那么这与ArrowLoop有什么关系呢?好吧,Netwire的ArrowLoop实例在传递的线路的第二个参数上运行mfix.换句话说,考虑循环的类型签名:

loop :: a (b, d) (c, d) -> a b c

在Netwire中,ArrowLoop的实例是:

instance MonadFix m => ArrowLoop (Wire s e m)

这意味着与电线一起使用时,循环功能的类型是:

loop :: MonadFix m => Wire s e m (b, d) (c, d) -> Wire s e m b c

由于循环不采用类型d的初始参数,这意味着无法通过线路初始化任何类型的传统“循环”.从中获取值的唯一方法是继续将输出应用为输入,直到找到终止条件,这类似于修复的工作方式.作为参数传递给循环的导线实际上不会多次执行,因为stepWire会使用不同的输入反复应用于同一条导线.只有当导线实际产生固定值时,功能才会生成另一根导线(其行为与第一根导线相同).

为了完整性,这里是我原始直觉的代码,用于循环应该如何工作,我将其命名为semiLoop:

semiLoop :: (Monad m, Monoid s, Monoid e) => c -> Wire s e m (a, c) (b, c) -> Wire s e m a b
semiLoop initialValue loopWire = let
  runLoop :: (Monad m, Monoid s, Monoid e) =>
             Wire s e m (a, c) (b, c) -> s -> a -> c -> m (Either e b, Wire s e m a b)
  runLoop wire ts ipt x = do
    (result, nextWire) <- stepWire wire ts (Right (ipt, x))
    case result of
      Left i -> return (Left i, mkEmpty)
      Right (value, nextX) ->
        return (Right value, mkGen $\ts' ipt' -> runLoop nextWire ts' ipt' nextX)
  in
   mkGen $\ts input -> runLoop loopWire ts input initialValue

编辑

在Petr提供的wonderful answer之后,延迟组合器对于防止循环组合器发散是至关重要的.延迟只是在使用上述循环的mfix部分中的下一个值的延迟之间创建单值缓冲区.因此,上面的halfLoop的相同定义是:

semiLoop :: (MonadFix m, Monoid s, Monoid e) =>
            c -> Wire s e m (a, c) (b, c) -> Wire s e m a b
semiLoop initialValue loopWire = loop $second (delay initialValue) >>> loopWire
点赞