Netwire 5 中的 Kleisli Arrow? [英] Kleisli Arrow in Netwire 5?
问题描述
我正在尝试使用 Haskell + Netwire 5 (+ SDL) 创建游戏.现在我正在处理输出部分,我想创建在某些游戏状态下读取的连线,并输出要在屏幕上进行 blitted 的 SDL 表面.
I am trying to create a game using Haskell + Netwire 5 (+ SDL). Now I am working on the output part, where I would like to create wires that read in some game state and output the SDL surfaces to be blitted on screen.
然而,问题在于 SDL 表面包含在 IO
monad 中,因此任何创建此类表面的函数都必须具有类型 a ->IO b
.当然,arr
不会从 a -> 构造一个
.然而,由于线路的类型签名是 Wire
m b(Monad m, Monoid e) =>Wire s e m a b
,它看起来很像 Kleisi Arrow,但我找不到合适的构造器来制作这种电线.
However, the problem is that SDL surfaces are contained in IO
monad, so any function that creates such surfaces must have type a -> IO b
. Of course, arr
does not construct a Wire
from a -> m b
. However, since the type signature of a wire is (Monad m, Monoid e) => Wire s e m a b
, it looks quite like a Kleisi Arrow, but I cannot find a suitable constructor for making such a wire.
我是 FRP 和 Arrows 的新手,并没有用 Haskell 编写很多程序,所以这可能不是实现图形输出的最佳方式.如果我从一开始就错了,请告诉我.
I am new to FRP and Arrows, and have not programmed a lot in Haskell, so this may not be the best way to implement the graphics output. If I am wrong from the beginning, please let me know.
一些相关的 SDL 函数:
Some SDL functions related:
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 接口以进行测试
Update 1
This code type checks, but now I am trying to interface it with SDL for testing
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 线.我还演示了如何驱动电线,结果非常简单.
Now, after playing around with Arrows, I will answer my own question
using the function putStrLn
. It has type String -> IO ()
, which is
a -> m b
, so the method should generalize to all Kleisli wires. I also illustrate how to drive the wire, and the result is amazingly simple.
整个代码都是用 Literate Haskell 编写的,所以只需复制并运行即可.
The entire code is written in Literate Haskell, so just copy it and run.
首先,Netwire 5 库有一些导入
First, there are some imports for the Netwire 5 library
import Control.Wire
import Control.Arrow
import Prelude hiding ((.), id)
现在,这是制作 Kleisli Wire 的核心.假设你有一个类型为 a -> 的函数m b
需要被提升成线.现在,注意 mkGen_
有类型mkGen_ :: Monad m =>(a -> m (e b)) ->线 s e m a b
Now, this is the core of making a Kleisli Wire. Assume you have a
function with type a -> m b
that needs to be lifted into a wire. Now,
notice that mkGen_
has type
mkGen_ :: Monad m => (a -> m (Either e b)) -> Wire s e m a b
所以,用 a 制作电线 ->m b
,我们首先需要得到一个函数类型 a ->m (() b)
.请注意, Left 禁止了连线,而 Right 激活它,所以内部是 Either () b
而不是要么是b()
.实际上,如果您尝试后者,则会出现一个模糊的编译错误会告诉你以错误的方式得到这个.
So, to make a wire out of a -> m b
, we first need to get a function
with type a -> m (Either () b)
. Notice that Left inhibits the wire,
while Right activates it, so the inner part is Either () b
instead of
Either b ()
. Actually, if you try the latter, an obscure compile error
will tell you get this in the wrong way.
获得 a ->m(Either()b)
,首先考虑如何得到m (Either () b)
from m b
,我们从monad (mb),将其提升到 Right,然后返回到 monad m.简而言之:mB >>= return .没错
.由于我们这里没有值mB",我们制作一个 lambda 表达式以获得 a ->m (Either () b)
:
To get a -> m (Either () b)
, first consider how to get
m (Either () b)
from m b
, we extract the value from the monad (m
b), lift it to Right, then return to the monad m. In short:
mB >>= return . Right
. Since we don't have the value "mB" here, we
make a lambda expression to get a -> m (Either () b)
:
liftToEither :: (Monad m) => (a -> m b) -> (a -> m (Either () b))
liftToEither f = a -> (f a >>= return . Right)
现在,我们可以制作 Kleisli 线了:
Now, we can make a Kleisli wire:
mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ a -> (f a >>= return . Right)
那么,让我们试试规范的hello, world"连线吧!
So, let's try the canonical "hello, world" wire!
helloWire :: Wire s () IO () ()
helloWire = pure "hello, world" >>> mkKleisli putStrLn
现在来说明如何驱动线的主要功能.笔记与 Control.Wire.Run
中 testWire
的源代码比较从 Netwire 库中,没有使用liftIO:外部程序对电线在内部如何工作一无所知.它只是步骤电线无视其中的内容.也许
这个Just
意味着更好与使用关于 Kleisli Wires 的 Nothing
相比?(没有双关语!)
Now comes the main function to illustrate how to drive the wire. Note
that comparing to the source of testWire
in the Control.Wire.Run
from the Netwire library, there is no use of liftIO: the outer program
knows nothing about how the wires work internally. It merely steps the
wires ignoring what is in it. Maybe
this Just
means better
composition than using Nothing
about Kleisli Wires? (No pun intended!)
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 不能很好地工作...
Now here comes the code. Unfortunately StackOverflow does not work quite well with 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
:
Thanks to Cubic's inspiration. liftToEither
can actually be written in, you guess it, liftM
:
liftToEither f = a -> liftM Right $ f a
mkKleisli f = mkGen_ $ a -> liftM Right $ f a
这篇关于Netwire 5 中的 Kleisli Arrow?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!