【问题标题】:How to model this recurisve structure in Haskell?如何在 Haskell 中建模这种递归结构?
【发布时间】:2020-02-11 04:07:04
【问题描述】:

我正在尝试通过 Haskell 类型系统对 kdb/q“原子和列表”进行建模。

在 kdb/q 中,所有数据都是从原子构建的。原子是特定数据类型的不可约值。 Int、boolean 和 char 是原子的示例。列表是从原子构建的有序集合。由于 q 是一种向量语言,大多数内置操作都是原子的,因此它会递归到参数结构中,直到它到达原子。

例如:

(1;2;3) 是整数 1、2、3 的简单列表

(1.0;2;(3;4;5)) 是 1.0(float)、2(int) 和简单 int 列表 (3;4;5) 的通用列表

neg 是一个对一个数字求反的函数。例如:

负 1 产生 -1

负 -1.0 产生 1f

neg (1.0;2;(3;4;5)) 产生 (-1f;-2;(-3;-4;-5))。

这就是启发我尝试在 Haskell 类型中建模这种行为的原因。数据类型应由原子类型和列表组成。

以下是我目前所拥有的简化版本。我还进一步尝试使其成为可折叠和可遍历的实例。

data Atom = I Int
          | C Char
          | D Double 
          deriving Show

data Q a = QAtom a 
         | QList [Q a]
         deriving Show

instance Functor Q where
    fmap f (QAtom a) = QAtom (f a)
    fmap f (QList qs) = QList $ fmap (fmap f) qs

instance Foldable Q where
    foldMap f (QAtom a) = f a
    foldMap f (QList qs) = mconcat $ fmap (foldMap f) qs

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList []) = pure $ QList []
    sequenceA (QList (qfa:qfas)) = concatL <$> (sequenceA qfa) <*> (sequenceA (QList qfas))
        where
            concatL (QAtom a) (QList qas) = QList ((QAtom a):qas)

这就是我所拥有的,它可以编译,但我并不特别喜欢 concatL 函数,它没有根据类型覆盖所有模式。一旦我开始向 Q 添加一个新的值构造函数 QDict [(Q Atom, Q a)],情况就会变得更糟。

我是否对原始数据正确建模?我是否应该尝试使其可遍历?但是,如果我需要将数据类型与 Maybe 或 Either 一起使用来建模错误,我认为 Traversable 是必要的。

感谢任何建议。

编辑:编辑 q 代码格式

【问题讨论】:

  • 初步建议:我将不带类型变量的Q 定义为data Q = QAtom Atom | QList [Q]。这允许您制作诸如QList [QAtom (D 1.0), QAtom (I 2), QList [QAtom (I 3), QAtom (I 4), QAtom (I 5)]] 之类的列表,就像在您的 Q 示例中一样;你不能用你当前的类型来表示这样的结构。但除此之外,我不知道;我将不得不多看看你的代码,看看我是否能找到其他可以提供建议的地方。 编辑:我错了,看下一条评论
  • 实际上,不,没关系:我将您的构造函数 QAtom a 误读为 QAtom (Atom a)。您当前对Q 的定义实际上很好!很抱歉造成混乱。
  • 至于您的Traversable 实例:我认为您绝对应该将其设为Foldable 等的实例,但正如您已经注意到您的Traversable 实例有点有趣。实现这种情况的更好方法是避免模式匹配,而是使用Traversable [] 实例:sequenceA (QList fl) = fmap QList $ sequenceA $ fmap sequenceA fl。 (您可以将其视为对QList 的每个单独元素进行排序,然后对给出的整个操作列表进行排序。)但我发现实现traverse 而不是sequenceA 要容易得多。
  • “列表是由原子构建的有序集合”这句话具有误导性。这在某种微不足道的意义上是正确的,因为 KDB 中的所有类型最终都是从原子构建的,但只有简单的列表是直接从原子构建的。无论如何,为了更好地理解 q 数据类型在 KDB 中是如何表示的,您可能需要查看 C 接口的 KDB 头文件(k.h,可以在 code.kx.com 上找到)。另一个有用的资源是github.com/exxeleron 上的 Erlang、Java、C# 和 Python 的 KDB 接口集合,它们表示这些语言中的 KDB/q 数据类型。

标签: haskell kdb algebraic-data-types traversable


【解决方案1】:

编译器知道如何为您的类型自动派生Traversable 实例。如果你先:set -ddump-deriv -dsuppress-all -XDeriveTraversable -XStandaloneDeriving 然后deriving instance Traversable Q,你可以看到“正确”的答案。如果您将这些知识应用到您的实例中,您会得到:

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList qfas) = fmap QList (traverse sequenceA qfas)

或者,如果您想避免使用traverse 而使用sequenceA

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList qfas) = fmap QList (sequenceA (fmap sequenceA qfas))

关键是列表本身是Traversable,因此您可以在它们上调用sequenceA,而无需以您自己的类型重新包装它们。


旁注,在您的Foldable 实例中,不要链接mconcatfmap,只需再次使用foldMap,因为列表也是Foldable

instance Foldable Q where
    foldMap f (QAtom a) = f a
    foldMap f (QList qs) = foldMap (foldMap f) qs

【讨论】:

  • 我不认为每种类型都最多有一个可遍历的实例。我能想到至少两个data Pair a = Pair a a
猜你喜欢
  • 2013-09-28
  • 2017-11-28
  • 2011-09-25
  • 2018-09-26
  • 2019-01-28
  • 2012-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多