2015-09-23 3 views
3

Я пытаюсь создать игру, используя Haskell + Netwire 5 (+ SDL). Теперь я работаю над выходной частью, где я хотел бы создавать проводы, которые читаются в каком-либо игровом состоянии, и выводить поверхности SDL, которые должны быть задействованы на экране.Kleisli Arrow в Netwire 5?

Однако проблема заключается в том, что поверхности SDL содержатся в монаде IO, поэтому любая функция, которая создает такие поверхности, должна иметь тип a -> IO b. Конечно, arr не создает Wire от a -> m b. Однако, поскольку сигнатура типа провода равна (Monad m, Monoid e) => Wire s e m a b, она выглядит как Kleisi Arrow, но я не могу найти подходящего конструктора для создания такого провода.

Я новичок в FRP и Arrows и не запрограммировал много в Haskell, так что это может быть не лучший способ реализовать выход графики. Если я ошибаюсь с самого начала, пожалуйста, дайте мне знать.

Некоторые СВД функции, связанные:

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() 

Update проверяет тип 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 
+0

Видимо, вы уже ответили на свой вопрос. Положите это как ответ, вместо того, чтобы добавить его к вашему вопросу. (В частности, вероятно, достаточно упомянуть 'mkGen_' и это тип ответа, конкретная реализация, вероятно, менее интересна для будущих читателей). Если у вас больше вопросов, задайте их отдельно, вместо того чтобы менять свои вопросы. – Cubic

+0

Нет, я еще не проверял, работает ли оно еще –

+0

Теперь я как бы проверил использование 'putStrLn' в качестве более простого примера. –

ответ

0

Теперь, после игры вокруг со стрелками, я отвечу на свой вопрос , используя функцию putStrLn. Он имеет тип String -> IO(), то есть a -> m b, поэтому метод должен обобщаться на все провода Kleisli. Я также иллюстрирую, как управлять проводом, и результат поразительно прост.

Весь код написан в грамотном Haskell, поэтому просто скопируйте его и запустите.

Во-первых, есть некоторая часть импорта для Netwire 5 библиотеки

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

Теперь это ядро ​​делает Клейсли 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 блокирует провод, , а правый активирует его, поэтому внутренняя часть Either() b вместо Either b(). На самом деле, если вы попробуете последний, то неясная ошибка компиляции скажет вам, что вы ошиблись.

Чтобы получить a -> m (Either() b), сначала рассмотрим, как получить m (Either() b) из m b, мы извлечь значение из монады (м б), поднимите его вправо, а затем вернуться к монаде м. Короче говоря: mB >>= return . Right.Поскольку мы не имеем значение «тВ» здесь, мы сделать лямбда-выражение, чтобы получить 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) 

Итак, давайте попробуем канонический «привет, мир»!

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

Теперь идет основная функция, чтобы проиллюстрировать, как вести провод. Примечание , что по сравнению с источником testWire в the Control.Wire.Run из библиотеки Netwire, нет никакой пользы liftIO: внешняя программа ничего не знает о том, как провода работают внутри. Он просто шагает по проводам , игнорируя то, что в нем. Maybe это Just означает лучше состав, чем при использовании Nothing о проводе Kleisli? (Нет каламбур!)

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

Теперь вот код. К сожалению StackOverflow не работает достаточно хорошо с грамотным 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' 

Update

Благодаря вдохновению Cubic-х. liftToEither фактически может быть написано, вы догадались, liftM:

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

Обратите внимание, что 'm >> = return. f' является просто 'fmap f m'. Также я не уверен, что вы имеете в виду, когда говорите, что putStrLn обобщает на все провода? – Cubic

+0

Не так ли? Работает ли fmap с Monads? –

+0

Когда я говорю, что это обобщает на все провода, я на самом деле имею в виду, что подобные вещи применяются для подъема всех функций 'a -> m b' в' Wire s e m a b' –

Смежные вопросы