Netwire 5 中的 Kleisli 箭头?
Kleisli Arrow in Netwire 5?
我正在尝试使用 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
现在,在玩过 Arrows 之后,我将回答我自己的问题
使用函数 putStrLn
。它的类型为 String -> IO ()
,即
a -> m b
,所以该方法应该推广到所有 Kleisli 线。我还演示了如何驱动电线,结果非常简单。
整个代码都是用 Literate Haskell 写的,所以只需复制它并 运行。
首先,有一些 Netwire 5 库的导入
import Control.Wire
import Control.Arrow
import Prelude hiding ((.), id)
现在,这是制作克莱斯利线的核心。假设你有一个
类型为 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),将它提升到右,然后 return 到 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)
现在,我们可以制作克莱斯利线了:
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
意味着更好
关于 Kleisli Wires 的组成比使用 Nothing
更重要? (没有双关语!)
main = go clockSession_ helloWire
where
go s w = do
(ds, s') <- stepSession s
(mx, w') <- stepWire w ds (Right ())
go s' w'
代码来了。不幸的是,Whosebug 不能很好地与 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
我正在尝试使用 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
现在,在玩过 Arrows 之后,我将回答我自己的问题
使用函数 putStrLn
。它的类型为 String -> IO ()
,即
a -> m b
,所以该方法应该推广到所有 Kleisli 线。我还演示了如何驱动电线,结果非常简单。
整个代码都是用 Literate Haskell 写的,所以只需复制它并 运行。
首先,有一些 Netwire 5 库的导入
import Control.Wire
import Control.Arrow
import Prelude hiding ((.), id)
现在,这是制作克莱斯利线的核心。假设你有一个
类型为 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),将它提升到右,然后 return 到 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)
现在,我们可以制作克莱斯利线了:
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
意味着更好
关于 Kleisli Wires 的组成比使用 Nothing
更重要? (没有双关语!)
main = go clockSession_ helloWire
where
go s w = do
(ds, s') <- stepSession s
(mx, w') <- stepWire w ds (Right ())
go s' w'
代码来了。不幸的是,Whosebug 不能很好地与 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