有时需要执行一些复杂的例程来检索或保存正在处理的数据。在这种情况下,人们想要分离数据生成和数据处理逻辑。常见的方法是使用类似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)很明显,增长和缩小操作非常无效,因为它们的复杂性与帧大小成正比。有没有更好的方法来扩展这样的拉链?
答案 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
类型,你可以用纯函数处理它,并且所有的加载逻辑都被分解为镜头。
我在手机上写了这个,所以可能有几个错误,但我会在以后修复它们。