addToLeftPlayerPos 的正确实现是什么

What is the correct implementation of addToLeftPlayerPos

我正在尝试按照 this paper 进行函数式响应式编程,但我仍然停留在第 4.2 节中的第二个示例上。

我得到了第一个例子 reader 变压器和 运行:

module Main where

import FRP.BearRiver    
import Control.Monad.Trans.MSF.Reader

type Game = Ball
type Ball = Int

type GameEnv = ReaderT GameSettings

data GameSettings
    = GameSettings
      { leftPlayerPos  :: Int
      , rightPlayerPos :: Int
      }

ballToRight :: Monad m => MSF (GameEnv m) () Ball
ballToRight =
    count >>> arrM addToLeftPlayerPos
    where
      addToLeftPlayerPos =
          (\n -> (n +) <$> asks leftPlayerPos)

hitRight :: Monad m => MSF (GameEnv m) Ball Bool
hitRight = arrM (\i -> (i >=) <$> asks rightPlayerPos)

但是下一步我很挣扎。我不知道如何正确地引入写入转换器,因为我什至无法编译:

module Main where

import FRP.BearRiver
import Control.Monad
import Control.Monad.Trans.MSF.Reader
import Control.Monad.Trans.MSF.Writer

type Game = Ball
type Ball = Int

type GameEnv m =
    WriterT [String] (ReaderT GameSettings m)

data GameSettings
    = GameSettings
      { leftPlayerPos  :: Int
      , rightPlayerPos :: Int
      }

ballToRight :: Monad m => MSF (GameEnv m) () Ball
ballToRight =
    count >>> arrM addLeftPlayerPos >>> arrM checkHitR
    where
      addLeftPlayerPos =
          (\n -> (n +) <$> asks leftPlayerPos)
      checkHitR n = do
          rp <- asks rightPlayerPos
when (rp > n) $ tell ["Ball is at " ++ (show n)]

实际上函数调用 addLeftPlayerPos 的那一行是有问题的,因为论文中没有给出函数 arrM addLeftPlayerPos 而且我的版本似乎缺少 WriterT 类型作为类型别名的签名 type GameEnv m ... 建议

addLeftPlayerPos 函数的正确实现是什么?

编辑: 编译器错误是:

 Expected type: MSF (GameEnv m) () Ball
    Actual type: MSF (ReaderT GameSettings m1) () () 
• In the expression: 
    count >>> arrM addToLeftPlayerPos >>> arrM checkHitR 

一些措辞

当你有一个转换器堆栈时,你必须 "lift" 你的操作到 运行 内部 monad 函数。例如:

type MyMonad a = Transformer1 (Transformer2 IO) a

这里的堆栈是IO的Transformer2的Transformer1。 "outer" monad 是 Transformer1,它用 IO 的最内层(或基础,底部)monad 包装 Transformer2。在您的情况下,堆栈实际上是某个未知 monad m 的 Reader 的 Writer,一切都很好。

现在,如果我们想从函数 g :: MyMonad 中 运行 f :: Transformer2 IO a,我们必须 lift f。同样,如果我们有 getLine :: IO String 并且我们希望 运行 从 g :: Transformer1 (Transformer2 IO) a 中得到它,那么我们可以 lift (lift getLine).

提升

如果导入 Control.Monad.Trans.Class,则可以提升 ReaderT 操作。例如 lift (asks ...) 而不是 asks ....

确实很有帮助。您评论的错误可能是由于在 checkHitR.

中使用了 asks

还有一个类型错误

完成提升后出现错误:

frosch.hs:23:5: error:
    • Couldn't match type ‘()’ with ‘Int’
      Expected type: MSF (GameEnv m) () Ball
        Actual type: MSF
                       (WriterT [[Char]] (ReaderT GameSettings m)) () ()

这是因为您的 checkHitR 没有 return 值 rp(我认为它应该)。解决这个问题给了我们最终的代码:

module Main where

import FRP.BearRiver
import Control.Monad.Trans.Class
import Control.Monad
import Control.Monad.Trans.MSF.Reader
import Control.Monad.Trans.MSF.Writer

type Game = Ball
type Ball = Int

type GameEnv m =
    WriterT [String] (ReaderT GameSettings m)

data GameSettings
    = GameSettings
      { leftPlayerPos  :: Int
      , rightPlayerPos :: Int
      }

ballToRight :: Monad m => MSF (GameEnv m) () Ball
ballToRight =
    count >>> arrM addLeftPlayerPos >>> arrM checkHitR
    where
      addLeftPlayerPos =
          (\n -> (n +) <$> lift (asks leftPlayerPos))
      checkHitR n = do
          rp <- lift (asks rightPlayerPos)
          when (rp > n) $ tell ["Ball is at " ++ (show n)]
          pure rp