7

有时需要执行一些复杂的例程来检索或保存正在处理的数据。在这种情况下,需要将数据生成和数据处理逻辑分开。常见的方法是使用类似迭代的功能。有很多不错的库:管道、导管等。在大多数情况下,它们会做这些事情。但是据我所知,它们(可能除了管道)受到处理顺序的限制。

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

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

-- 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) 很明显,增长和收缩操作非常无效,因为它们的复杂性与帧大小成正比。有没有更好的方法来延长这样的拉链?

4

1 回答 1

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 箭头一样简单::

sequenceOf art >=> sequenceOf sound
    :: Asset (IO Art) (IO Sound) -> IO (Asset Art Sound)

...当然,您可以嵌套资产并组合镜头以访问嵌套资产,并且一切仍然“正常工作”。

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

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

于 2014-03-21T15:16:58.700 回答