网络5中的Kleisli Arrow?

时间:2015-09-23 17:33:23

标签: haskell functional-programming frp arrows netwire

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

然而,问题是SDL表面包含在IO monad中,因此创建此类表面的任何函数都必须具有类型a -> IO b。当然,arr不会从Wire构建a -> m b。但是,由于线的类型签名是(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

1 个答案:

答案 0 :(得分: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的{​​{1}},我们从monad中提取值(m b),将其提升到右边,然后返回monad m。简而言之: mB >>= return . Right。因为我们没有价值&#34; mB&#34;在这里,我们 创建一个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)

所以,让我们试试规范&#34;你好,世界&#34;线!

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

现在主要功能是说明如何驱动电线。注意 与testWirethe Control.Wire.Run的来源进行比较 来自Netwire库,没有使用liftIO:外部程序 什么都不知道电线如何在内部工作。它只是步骤 电线忽略了它里面的东西。 MaybeJust意味着更好 组成比使用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