Клейсли Стрелка в 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
Другие вопросы по тегам