分离数据加载/卸载和处理逻辑

时间:2014-03-21 07:33:31

标签: haskell haskell-pipes

有时需要执行一些复杂的例程来检索或保存正在处理的数据。在这种情况下,人们想要分离数据生成和数据处理逻辑。常见的方法是使用类似iteratee的功能。有很多不错的图书馆:管道,管道等。在大多数情况下,他们会做这件事。但是AFAIK它们(除了,可能是管道)受到处理顺序的限制。

但考虑一个日志查看器示例:人类可能希望随机来回漫步。他也可以放大和缩小。我担心迭代者在这里无能为力。

简单的解决方案可能如下所示:

-- True is for 'right', 'up', etc. and vice versa 
type Direction = Bool

class Frame (f :: * -> *) where
  type Dimension f :: *

  type Origin f :: * -> *

  grow', shrink' move' :: Monad m => Dimension f -> Direction -> f a -> m (f a)

  move' dim dir f = grow' dim dir f >>= shrink' dim (not dir)

  liftF' :: (Origin f a -> b) -> f a -> b

class Frame f => MFrame f where
  liftMF' :: (Origin f a -> (b, Origin f a)) -> f a -> (b, f a)

-- Example instance: infinite stream.
data LF a = LF [a] [a] [a]

instance Frame LF where
  type Dimension LF = () -- We have only one dimension to move in...
  type Origin LF = [] -- User see piece of stream as a plain list

  liftF' f (LF _ m _) = f m

  grow' () True (LF l m (h:r)) = return $ LF l (m++[h]) r
  ...

然后可以将其包装到StateT中,依此类推。所以,问题:

0)我是否完全错过了迭代的重点,它们适用于此?

1)我刚刚重新发明了一个众所周知的车轮吗?

2)很明显,增长和缩小操作非常无效,因为它们的复杂性与帧大小成正比。有没有更好的方法来扩展这样的拉链?

1 个答案:

答案 0 :(得分:5)

你想要镜头,特别是sequenceOf功能。以下是3元组目标加载的示例:

 sequenceOf _2 :: (IO a, IO b, IO c) -> IO (IO a, b, IO c)

sequenceOf将镜头带到包含加载操作的多态字段,运行操作,然后用操作结果替换该字段。您可以在自己的自定义类型上使用sequenceOf,只需在要加载的字段中设置类型多态,如下所示:

data Asset a b = Asset
    { _art :: a
    , _sound :: b
    }

...并且还让你的镜头使用完整的四种类型参数(这是它们存在的一个原因):

art :: Lens (Asset a1 b) (Asset a2 b) a1 a2
art k (Asset x y) = fmap (\x' -> Asset x' y) (k x)

sound :: Lens (Asset a b1) (Asset a b2) b1 b2
sound k (Asset x y) = fmap (\y' -> Asset x y') (k y)

...或者您可以使用makeLenses自动生成镜头,它们将足够通用。

然后你可以写:

sequenceOf art :: Asset (IO Art) b -> IO (Asset Art b)

...加载多个资产就像组成Kleisli arrows ::

一样简单
sequenceOf art >=> sequenceOf sound
    :: Asset (IO Art) (IO Sound) -> IO (Asset Art Sound)

...当然,你可以嵌套资产并组合镜头以达到嵌套资产,而且所有东西仍然可以使用#34;只是工作"。

现在你有了一个纯粹的Asset类型,你可以用纯函数处理它,并且所有的加载逻辑都被分解为镜头。

我在手机上写了这个,所以可能有几个错误,但我会在以后修复它们。