Haskell 数据加载/卸载与处理逻辑的分离

Haskell 数据加载/卸载与处理逻辑的分离,haskell,haskell-pipes,Haskell,Haskell Pipes,有时,为了检索或保存正在处理的数据,需要执行一些复杂的例程。在这种情况下,需要将数据生成和数据处理逻辑分开。常用的方法是使用类似iteratee的功能。有很多像样的库:管道、导管等。在大多数情况下,它们可以完成这项工作。但它们(可能除了管道)受到加工顺序的限制 但是考虑一个日志查看器的例子:人类可能想要随机地来回漫游。他也可以放大和缩小。恐怕你在这里帮不了忙 简单的解决方案可能如下所示: -- True is for 'right', 'up', etc. and vice versa typ

有时,为了检索或保存正在处理的数据,需要执行一些复杂的例程。在这种情况下,需要将数据生成和数据处理逻辑分开。常用的方法是使用类似iteratee的功能。有很多像样的库:管道、导管等。在大多数情况下,它们可以完成这项工作。但它们(可能除了管道)受到加工顺序的限制

但是考虑一个日志查看器的例子:人类可能想要随机地来回漫游。他也可以放大和缩小。恐怕你在这里帮不了忙

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

-- 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
  ...
data Asset a b = Asset
    { _art :: a
    , _sound :: b
    }
然后我们可以把它包装成StateT等等。因此,问题是:

0)我是否完全忽略了迭代对象的要点,它们在这里是适用的

1) 我是不是重新发明了一个著名的轮子


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

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

 sequenceOf _2 :: (IO a, IO b, IO c) -> IO (IO a, b, IO c)
sequenceOf
将镜头移到包含加载操作的多态字段,运行该操作,然后用操作结果替换该字段。您可以在自己的自定义类型上使用
sequenceOf
,只需在要加载的字段中使类型具有多态性,如下所示:

-- 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
  ...
data Asset a b = Asset
    { _art :: a
    , _sound :: b
    }
。。。并使您的镜头使用完整的四种类型参数(这是它们存在的原因之一):

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

然后你可以写:

sequenceOf art :: Asset (IO Art) b -> IO (Asset Art b)
。。。加载多个资产就像编写Kleisli箭头一样简单:

sequenceOf art >=> sequenceOf sound
    :: Asset (IO Art) (IO Sound) -> IO (Asset Art Sound)
。。。当然,您可以嵌套资源并组合镜头以访问嵌套资源,而所有内容仍然“正常工作”

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


我在手机上写了这封信,所以可能会出现一些错误,但我稍后会修复它们。

一个非常具体的评论:您可能希望将函数
中的monad增长“
收缩“
移动”
作为关联类型。如果你像现在一样一直保持它,你最好不要一个单子,因为它几乎没有什么有趣的东西可以用。