【问题标题】:Generic type transformations in HaskellHaskell 中的泛型类型转换
【发布时间】:2011-08-15 23:38:39
【问题描述】:

我正在尝试编写一个arrow 转换器,它采用常规函数,并将它们转换为抽象值的计算。如果我们有一个“源”箭头,

f :: Int -> Int
f x = x + 1

那么目标是让 f 处理提升的 [sic?] 抽象值类型,在这个例子中

f' :: AV Int -> AV Int
f' (Const x) = Const (f x)
-- pass along errors, since AV computation isn't always defined
-- or computable in the case of errors
f' (Error s) = Error s
-- avRep = "abstract representation". Think of symbolic math manipulation or ASTs.
f' (Abstract avRep) = AVRepPlus avRep (AVRepConst 1)

然而,为了成功地实现这个箭头,需要提升几个类型,以便在任意深度具有具有一些具体值和一些抽象值的异构数据结构。我最终做的是为常规的 haskell 构造函数添加特殊类型,例如如果

g = uncurry (+) -- i.e. g (x, y) = x + y

然后我为元组构造函数 (,) 添加一个抽象表示,

AVTuple :: AV a -> AV b -> AV (a, b)

g 的代码被提升到 [展开一点],

g' (AVTuple (AVConst a) (AVConst b)) = (AVConst (g (a, b)))
g' (AVTuple (AVError e) _) = (AVError e)
-- symmetric case here, i.e. AVTuple _ (AVError e)
g' (AVTuple a@(AVTuple _ _) b) = -- recursive code here

AVEither 也需要这样做。这最终会成为很多案例。有什么好办法吗?

我是 Haskell 新手,所以请发给我参考资料或半详细的解释;可能我读过的最接近的东西是 SYBR 论文(废弃你的样板革命)第 1-3 节。

非常感谢!

【问题讨论】:

  • 箭头在这里可能不是很有用。 Functor-Applicative-Monad 类集共享一个操作 fmap(对于 Monad 称为 liftM,对于 Applicative 称为 liftA),它将类型转换函数映射到 Functor“内部”的一个或多个元素上。箭头是一种非常通用的结构,因此不支持这样的操作。
  • stephen tetley 似乎是正确的,Arrow 作为一个类型类,不是你想要的。不过,我无法清楚地遵循您的目的。请注意,f' 定义的前两位建议将AV a s 定义为Error s | Concrete a。这是Either 类型,您定义f' 的那些行使其成为我们都使用的Either 的标准fmap f。也许对不朽的 Typeclassopedia (haskell.org/wikiupload/8/85/TMR-Issue13.pdf) 的研究会让你更容易地交流你的目的?同样,我只是在开发一点stephen tetleys 评论。

标签: generics haskell types arrows algebraic-data-types


【解决方案1】:

让我看看我是否明白你在这里的目的。你有一个类型AV a,它描述了一个产生a类型的计算,该计算的结构可以以允许检查的方式保存。您想要一种将任意函数提升到 AV 上的操作的方法,保留结构,而不必为每个操作创建特殊情况。

通常,要将函数提升到某种结构中,可以使用FunctorApplicative。但是,这样做的直接方法涉及转换结构并直接应用提升的函数,而不是将函数应用程序保留为结构的一部分。

你想要的更尴尬,原因如下:

假设我们有一些想要提升的函数,以及两个适当类型的抽象值来应用它:

x :: AV A
x = ...

y :: AV B
y = ...

f :: A -> B -> C
f = ...

假设存在一个函数liftAV2 可以满足您的要求。我们希望lift2 f 的类型为AV A -> AV B -> AV C,就像liftA 对应Applicative

稍后,我们希望通过恢复fxy 的值来检查使用lift2 f 生成的计算。假设现在我们只想提取第一个参数。假设存在一个执行此操作的函数extractArg1,使得extractArg1 (liftAV2 f x y) = xextractArg1 的类型是什么?在这里,在 context 中,我们知道它的类型应该是 AV C -> AV A。但它一般有什么类型呢? AV c -> AV a 之类的东西?这是错误的,因为结果不仅仅是 any 类型 a,它是用于构造 AV c 值的任何类型。假设我们正在操作的值是使用liftAV2 f 的结果构造的,我们知道所讨论的类型存在,但我们一般无法找到它。

这是我们进入的地方,恰如其分地,存在类型。诚实地使用它们,而不是像通常那样将它们与类型类一起滥用。

你也许可以通过一些努力来完成你所追求的,但这已经进入了相当高级的领域。您可能希望使用 GADTs 作为初学者,但我认为您可能已经在这样做了。使用存在类型也往往非常笨拙,因为您只能在有限的上下文中知道它们是什么。

在您的具体情况下,给AV 两个类型参数可能更容易:一个代表计算的最终类型,一个代表计算的结构,例如:

data f :$ x = ...

data AV structure result where
    ...
    AVApply :: AV f (a -> b) -> AV x a -> AV (f :$ x) b

然后,为了检查计算,您可以查看第一种类型以了解您拥有什么;为了构建计算,您可以查看第二个以确保类型匹配。评估函数将具有类似AV t a -> a 的类型,丢弃结构。你也可以使用结构类型“解包”计算,丢弃结果类型,如果你需要拆开结构以便漂亮地打印它。

【讨论】:

    【解决方案2】:

    我喜欢这样想,当我想谈论一些“额外的数据”时,我会使用Functor 实例(取决于“额外的一点”是什么,我实际上可能是谈论ApplicativeMonad)。

    另一方面,我使用Arrow 实例来谈论“少一点的函数”:箭头让您可以定义可以像函数一样组合在一起的事物,但是您可以添加额外的结构或禁止某些结构的限制(例如没有ArrowChoiceArrowLoop 的箭头)。

    您希望完成什么并不完全清楚,但听起来您实际上是在将数据包装在 AV 类型的构造函数中。在这种情况下,您可能希望将AV 设为Functor 的实例,并为(AV a, AV b) => AV (a, b) 添加Functor 实例,类似地为AV 添加环绕Either 的实例。

    【讨论】:

      猜你喜欢
      • 2012-11-28
      • 1970-01-01
      • 1970-01-01
      • 2022-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多