Haskell 自定义类型的 `bind` 实例

Haskell instance of `bind` for a custom type

我正在尝试为自定义类型 ST a

的绑定运算符 (>>=) 创建一个实例

我找到了这种方法,但我不喜欢那种硬编码 0

有没有什么方法可以在不使用硬编码 0 并且不考虑函数类型的情况下实现它?

newtype ST a = S (Int -> (a, Int))
    
-- This may be useful to implement ">>=" (bind), but it is not mandatory to use it
runState :: ST a -> Int -> (a, Int)
runState (S s) = s
        
instance Monad ST where
      return :: a -> ST a
      return x = S (\n -> (x, n))
       
      (>>=) :: ST a -> (a -> ST b) -> ST b
      s >>= f = f (fst (runState s 0))

要牢记的最重要的一点是,您的 ST 类型是 函数 的包装器。如果您开始定义为 (>>=) = \s -> \f -> S (\n -> ... ) 会怎样?在那里为 sf 参数编写单独的 lambda 可能(好吧,是)有点傻,但我这样做是为了表明它们与 n 参数。您可以在 (>>=).

的定义中使用它

我经常发现使用某种类型的伪代码重写更容易遵循此类代码,如下所示:从

开始
instance Monad ST where
      return :: a -> ST a
      return x = S (\n -> (x, n))

我们到了

  runState (return x) n = (x, n)

表达的完全一样。它现在是一种定义,通过它必须遵循的相互作用定律。这让我可以忽略围绕基本内容的“噪音”/包装。

同理,那么,我们有

      (>>=) :: ST a -> (a -> ST b) -> ST b
      s >>= f = -- f (fst (runState s 0))   -- nah, 0? what's that?
      -- 
      -- runState (s >>= f) n = runState (f a) i where 
      --                                   (a, i) = runState s n
      --
                S $       \ n ->       let (a, i) = runState s n in
                                runState (f a) i

因为现在我们看到了 Int(即在范围内),n,当组合计算 提供给我们 s >>= f 将“运行”。我的意思是,什么时候 runState.

当然,在 main 调用之前,运行 实际上什么也没有。但它可以成为一个有用的比喻。

我们定义它的方式既是最简单的也是最通用的,这通常是要走的路。不过,还有更多方法可以使类型适合。

一个是使用 n 两次,在第二个 runState 的输入中也是如此,但这将使 i 挂起而未被使用。

另一种方法是将时间箭头翻转 w.r.t。状态通过,

                S $       \ n ->       let (a, i2) = runState s i 
                                           (b, i ) = runState (f a) n
                                        in (b, i2)

至少可以说这有点奇怪。 s 仍然 运行 首先(正如 s >>= f 组合所预期的那样)产生值 af 从中创建第二个计算阶段,但是状态正在向相反方向传递。