【问题标题】:Kleisli Arrow in Netwire 5?Netwire 5中的Kleisli Arrow?
【发布时间】:2015-12-21 03:31:11
【问题描述】:

我正在尝试使用 Haskell + Netwire 5 (+ SDL) 创建游戏。现在我正在处理输出部分,我想创建在某些游戏状态下读取的连线,并将 SDL 表面输出到屏幕上。

但是,问题在于 SDL 表面包含在 IO monad 中,因此创建此类表面的任何函数都必须具有类型 a -> IO b。当然,arr 不会从a -> m b 构造一个Wire。但是,由于线的类型签名是(Monad m, Monoid e) => Wire s e m a b,它看起来很像 Kleisi Arrow,但我找不到合适的构造函数来制作这样的线。

我是 FRP 和 Arrows 的新手,并且没有在 Haskell 中进行过很多编程,所以这可能不是实现图形输出的最佳方式。如果我从一开始就错了,请告诉我。

一些SDL功能相关:

createRGBSurfaceEndian :: [SurfaceFlag] -> Int -> Int -> Int -> IO Surface

fillRect :: Surface -> Maybe Rect -> Pixel -> IO Bool

blitSurface :: Surface -> Maybe Rect -> Surface -> Maybe Rect -> IO Bool

flip :: Surface -> IO ()

更新 1

此代码类型检查,但现在我正在尝试将其与 SDL 接口以进行测试

wTestOutput :: (Monoid e) => Wire s e IO () SDL.Surface
wTestOutput = mkGen_ $ \a -> (makeSurf a >>= return . Right)
    where
      makeSurf :: a -> IO SDL.Surface
      makeSurf _ = do
        s <- SDL.createRGBSurfaceEndian [SDL.SWSurface] 800 600 32
        SDL.fillRect s (Just testRect) (SDL.Pixel 0xFF000000)
        return s
      testRect = SDL.Rect 100 100 0 0

【问题讨论】:

  • 显然您已经回答了自己的问题。将其作为答案,而不是将其添加到您的问题中。 (特别是,提及mkGen_ 可能就足够了,它是答案中的类型,对于未来的读者来说,特定的实现可能不太有趣)。如果您有更多问题,请单独提问,而不是更改您的问题。
  • 不,我还没有验证它是否有效
  • 现在我用putStrLn作为一个更简单的例子进行了验证。
  • 电线和 FRP 通常是对时变值进行建模的一种方式。他们不应该真正“创造”任何东西。正如您编写的那样,您的程序将在每次评估电线时创建一个新的 SDL 表面!
  • 之前未使用的表面会被垃圾回收吗?当然,从语义上讲,更新同一个表面是一样的(无论如何都是状态),但我认为这些是实现细节。

标签: haskell functional-programming frp arrows netwire


【解决方案1】:

现在,在玩完 Arrows 之后,我将回答我自己的问题 使用函数putStrLn。它的类型为String -&gt; IO (),即 a -&gt; m b,因此该方法应推广到所有 Kleisli 线。我还演示了如何驱动电线,结果非常简单。

整个代码是用 Literate Haskell 编写的,所以只需复制并运行即可。

首先,有一些 Netwire 5 库的导入

import Control.Wire
import Control.Arrow
import Prelude hiding ((.), id)

现在,这是制作 Kleisli Wire 的核心。假设你有一个 类型为a -&gt; m b 的函数,需要提升到电线中。现在, 注意mkGen_ 有类型 mkGen_ :: Monad m =&gt; (a -&gt; m (Either e b)) -&gt; Wire s e m a b

所以,要从a -&gt; m b 中创建一条线,我们首先需要获取一个函数 类型为a -&gt; m (Either () b)。请注意,Left 抑制了连线, 而 Right 激活它,所以内部是 Either () b 而不是 Either b ()。实际上,如果您尝试后者,则会出现一个晦涩的编译错误 会告诉你弄错了。

要获取a -&gt; m (Either () b),首先考虑如何获取 m (Either () b) from m b,我们从 monad (m b),将其提升到 Right,然后返回到 monad m。简而言之: mB &gt;&gt;= return . Right。因为我们这里没有值“mB”,所以我们 制作一个 lambda 表达式以获取 a -&gt; m (Either () b):

liftToEither :: (Monad m) => (a -> m b) -> (a -> m (Either () b))
liftToEither f = \a -> (f a >>= return . Right)

现在,我们可以制作 Kleisli 线了:

mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ \a -> (f a >>= return . Right)

那么,让我们试试经典的“hello, world”连线吧!

helloWire :: Wire s () IO () ()
helloWire = pure "hello, world" >>> mkKleisli putStrLn

现在主要功能来说明如何驱动电线。笔记 与the Control.Wire.Run 中testWire 的来源相比 来自Netwire库,没有使用liftIO:外部程序 对电线内部的工作原理一无所知。它只是一步一步 电线忽略其中的内容。 Maybe 这个Just 意味着更好 作文比使用Nothing 关于 Kleisli Wires? (没有双关语!)

main = go clockSession_ helloWire
    where
      go s w = do
        (ds, s') <- stepSession s
        (mx, w') <- stepWire w ds (Right ())
        go s' w'

现在代码来了。不幸的是,StackOverflow 不能很好地与 Literate Haskell 配合使用......

{-# LANGUAGE Arrows #-}

module Main where

import Control.Wire
import Control.Monad
import Control.Arrow
import Prelude hiding ((.), id)

mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ \a -> liftM Right $ f a

helloWire :: Wire s () IO () ()
helloWire = pure "hello, world" >>> mkKleisli putStrLn

main = go clockSession_ helloWire
    where
      go s w = do
        (ds, s') <- stepSession s
        (mx, w') <- stepWire w ds (Right ())
        go s' w'

更新

感谢 Cubic 的启发。 liftToEither其实可以写成,你猜,liftM:

liftToEither f = \a -> liftM Right $ f a
mkKleisli f = mkGen_ $ \a -> liftM Right $ f a

【讨论】:

  • 请注意m &gt;&gt;= return . f 只是fmap f m。另外,我不确定您说 putStrLn 泛化到所有电线是什么意思?
  • 当我说它泛化到所有线路时,我实际上的意思是类似的事情适用于将所有 a -&gt; m b 函数提升到 Wire s e m a b
  • 对,\a -&gt; fmap Right $ f a 也可以。但我认为\a -&gt; (f a &gt;&gt;= return . Right) 可能为此目的表达得更好?
  • 不,抱歉,fmap 似乎不适用于 Monads。编译器告诉我添加Functor m,但这是不正确的。
  • 好的,应该是 liftM 而不是 fmap。我会在我的答案中添加这个
猜你喜欢
  • 2014-03-08
  • 2015-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-02
  • 2015-09-08
  • 2011-02-03
相关资源
最近更新 更多