Идея
Здравствуйте! Я пытаюсь реализовать в Haskell библиотеку обработки изображений на основе идеологии потока данных. У меня проблема, связанная с тем, как я хочу обрабатывать поток управления.
Основная идея - ввести time
. time
- это Float
, к которому можно получить доступ в любом месте кода (вы можете думать о нем, как о государственной монаде, но немного смешнее). Самое забавное в том, что мы можем использовать операцию timeShift
по результатам, влияя на время, которое будут видеть соответствующие операции.
Пример лучше всего объяснить эту ситуацию. Позволяет использовать следующую диаграмму потока данных:
-- timeShift(*2) --
-- / \
-- readImage -- addImages -> out
-- \ /
-- blur ----------
и его псевдокод (который не является typecheck - его не важно, если мы используем do или let notation здесь, идея должна быть ясной):
test = do
f <- frame
a <- readImage $ "test" + show f + ".jpg"
aBlur <- blur a
a' <- a.timeShift(*2)
out <- addImage aBlur a'
main = print =<< runStateT test 5
5
- это time
, с которым мы хотим запустить функцию test
. Функция timeShift
влияет на все операции слева от нее (на диаграмме потока данных) - в этом случае функция readImage
будет выполняться дважды - для обеих ветвей - нижняя будет использовать фрейм 5
, а верхний один кадр 5*2 = 10
.
Проблема
Я предоставляю здесь очень простую реализацию, которая отлично работает, но есть некоторые оговорки, которые я хочу решить. Проблема в том, что я хочу сохранить порядок всех операций ввода-вывода. Посмотрите, например, на нижнюю часть, которая пояснит, что я имею в виду.
Пример реализации
Ниже приведен пример реализации алгоритма и кода, который строит следующий граф потока данных:
-- A --- blur --- timeShift(*2) --
-- \
-- addImages -> out
-- /
-- B --- blur --------------------
код:
import Control.Monad.State
-- for simplicity, lets assume an Image is just a String
type Image = String
imagesStr = ["a0","b1","c2","d3","e4","f5","g6","h7","i8","j9","k10","l11","m12","n13","o14","p15","q16","r17","s18","t19","u20","v21","w22","x23","y24","z25"]
images = "abcdefghjiklmnoprstuwxyz"
--------------------------------
-- Ordinary Image processing functions
blurImg' :: Image -> Image
blurImg' img = "(blur " ++ img ++ ")"
addImage' :: Image -> Image -> Image
addImage' img1 img2 = "(add " ++ img1 ++ " " ++ img2 ++ ")"
--------------------------------
-- Functions processing Images in States
readImage1 :: StateT Int IO Image
readImage1 = do
t <- get
liftIO . putStrLn $ "[1] reading image with time: " ++ show t
return $ imagesStr !! t
readImage2 :: StateT Int IO Image
readImage2 = do
t <- get
liftIO . putStrLn $ "[2] reading image with time: " ++ show t
return $ imagesStr !! t
blurImg :: StateT Int IO Image -> StateT Int IO Image
blurImg img = do
i <- img
liftIO $ putStrLn "blurring"
return $ blurImg' i
addImage :: StateT Int IO Image -> StateT Int IO Image -> StateT Int IO Image
addImage img1 img2 = do
i1 <- img1
i2 <- img2
liftIO $ putStrLn "adding images"
return $ addImage' i1 i2
timeShift :: StateT Int IO Image -> (Int -> Int) -> StateT Int IO Image
timeShift img f = do
t <- get
put (f t)
i <- img
put t
return i
test = out where
i1 = readImage1
j1 = readImage2
i2 = blurImg i1
j2 = blurImg j1
i3 = timeShift i2 (*2)
out = addImage i3 j2
main = do
print =<< runStateT test 5
print "end"
Вывод:
[1] reading image with time: 10
blurring
[2] reading image with time: 5
blurring
adding images
("(add (blur k10) (blur f5))",5)
"end"
и должен быть:
[1] reading image with time: 10
[2] reading image with time: 5
blurring
blurring
adding images
("(add (blur k10) (blur f5))",5)
"end"
Обратите внимание на, что правильный вывод ("(add (blur k10) (blur f5))",5)
- это означает, что мы добавили изображение k10
в f5
- из соответственно 10-го и 5-го кадров.
Дополнительные требования
Я ищу решение, которое позволит пользователям писать простой код (например, в функции test
- это может быть, конечно, в Monad), но я не хочу, чтобы они вручную обрабатывали логику сдвига во времени.
Выводы
Единственное различие - порядок выполнения операций ввода-вывода. Я хотел бы сохранить порядок операций ввода-вывода так же, как они написаны в функции test
. Я пытался реализовать идею, используя Countinuations
, Arrows
и некоторые смешные состояния, но безуспешно.