【问题标题】:Misunderstanding ArrowLoop when used with Netwire与 Netwire 一起使用时对 ArrowLoop 的误解
【发布时间】:2014-08-05 04:15:54
【问题描述】:

按照this post 中出色答案的引导,我试图获得一个不使用箭头符号的ArrowLoop 工作示例。在我完全理解箭头在引擎盖下的工作原理之前,我对使用箭头表示法感到不舒服。话虽如此,我已经构建了一个基于我对箭头的(有限)理解的小程序应该可以工作。然而,它最终以可怕的<<loop>> 异常终止:

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)

我的直觉告诉我,&lt;&lt;loop&gt;&gt; 异常通常发生在您不向循环提供初始值时。我不是在包含fn i 0.0 的行中做到了这一点吗?输出不一致:

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

有谁知道我做错了什么?

【问题讨论】:

  • > 是 GHC 告诉您它已检测到评估循环的方式;一个表达式,在 GHC 评估期间它会返回到相同的 thunk。
  • 好吧,我想进一步解释一下为什么会发生这种情况,因为我不希望它发生。

标签: haskell frp arrows netwire


【解决方案1】:

主要的混淆点似乎是ArrowLoopmfix 之间的整体关系。对于外行来说,fix 是一个给定函数的finds the fixed point 函数:

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

mfix 是该函数的一元扩展,其类型签名不出所料:

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

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

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

在 Netwire 中,ArrowLoop 的实例是:

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

这意味着loop 函数在与电线一起使用时的类型是:

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

由于loop 不采用d 类型的初始参数,这意味着无法通过线路初始化任何类型的常规“循环”。从中获取值的唯一方法是继续将输出用作输入,直到找到终止条件,这类似于fix 的工作方式。作为参数传递给loop 的线实际上不会超过一次,因为stepWire 通过不同的输入一遍又一遍地应用于同一根线。只有当连线实际产生一个固定值时,函数才会步进并产生另一条连线(其行为方式与第一个相同)。

为了完整起见,这里是我对 loop 应该如何工作的最初直觉的代码,我将其命名为 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 之后,delay 组合器对于防止 loop 组合器发散至关重要。 delay 只是在上述循环的 mfix 部分中使用下一个值的惰性之间创建一个单值缓冲区。因此,上述semiLoop 的相同定义是:

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

【讨论】:

    猜你喜欢
    • 2014-07-06
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2021-01-25
    • 1970-01-01
    • 2022-12-12
    • 2013-12-18
    相关资源
    最近更新 更多