3

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

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

我是 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
4

1 回答 1

1

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

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

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

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

现在,这是制造 Kleisli Wire 的核心。假设您有一个a -> m b需要提升为电线的类型的函数。现在,请注意mkGen_具有类型 mkGen_ :: Monad m => (a -> m (Either e b)) -> Wire s e m a b

因此,要使用 制作电线a -> m b,我们首先需要获得一个类型为 的函数a -> m (Either () b)。请注意,Left 抑制了线,而 Right 激活了它,因此内部部分是Either () b而不是 Either b ()。实际上,如果你尝试后者,一个模糊的编译错误会告诉你以错误的方式得到它。

要获取a -> m (Either () b),首先考虑如何从 获取 m (Either () b)m b我们从 monad (m b) 中提取值,将其提升到 Right,然后返回到 monad m。简而言之: mB >>= return . Right。由于我们这里没有值“mB”,我们创建一个 lambda 表达式来获取a -> 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

现在是主要功能来说明如何驱动电线。请注意,与 Netwire 库中的testWirein源代码相比the Control.Wire.Run ,没有使用 liftIO:外部程序对内部线路的工作方式一无所知。它只是踩着电线,而忽略了里面的东西。MaybeJust意味着比使用NothingKleisli 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
于 2015-09-29T02:42:01.600 回答