Клейсли Стрелка в Netwire 5?
Я пытаюсь создать игру, используя Haskell + Netwire 5 (+ SDL). Сейчас я работаю над выходной частью, где я хотел бы создать проводники, которые читают в каком-то игровом состоянии и выводят поверхности SDL, которые будут скрыты на экране.
Однако проблема в том, что поверхности SDL содержатся в IO
монада, поэтому любая функция, которая создает такие поверхности, должна иметь тип a -> IO b
, Конечно, arr
не строит Wire
от a -> m b
, Тем не менее, так как тип подписи провода (Monad m, Monoid e) => Wire s e m a b
Это выглядит как стрелка Кляйси, но я не могу найти подходящий конструктор для создания такой проволоки.
Я новичок в 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 ответ
Теперь, поиграв со стрелками, я отвечу на свой вопрос с помощью функции putStrLn
, Имеет тип String -> IO ()
, которыйa -> m b
Таким образом, метод должен распространяться на все провода Клейсли. Я также иллюстрирую, как управлять проводом, и результат удивительно прост.
Весь код написан на 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)
, Обратите внимание, что Левый блокирует провод, в то время как Правый активирует его, поэтому внутренняя часть Either () b
вместоEither b ()
, На самом деле, если вы попробуете последнее, неясная ошибка компиляции скажет, что вы поняли это неправильно.
Получить a -> m (Either () b)
сначала подумай как получитьm (Either () b)
от m b
, мы извлекаем значение из монады (m
b), поднимаем его вправо, затем возвращаемся к монаде m. Короче:mB >>= return . Right
, Так как здесь у нас нет значения "mB", мы делаем лямбда-выражение, чтобы получить 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
о Клейсли Проводах? (Не каламбур!)
main = go clockSession_ helloWire
where
go s w = do
(ds, s') <- stepSession s
(mx, w') <- stepWire w ds (Right ())
go s' w'
Теперь здесь идет код. К сожалению, Stackru не очень хорошо работает с 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