一种在没有类型运算符的情况下模拟联合类型的方法



我在使用类型运算符之前已经这样做了,但我想排除这些,因为我希望能够用更小的锤子做到这一点,因为我实际上想用另一种语言来做,而且我不太确定类型运算符是否完全符合我的需求。

设置是两种数据类型,整数和...

> data Rational = Rational Integer Integer deriving Show

具有合理实例的两个类型类...

> class Divide2 a where
>   divide2 :: a -> a
> class Increment a where
>   inc :: a -> a
> instance Increment Main.Rational where
>   inc (Rational a b) = Rational (a + b) b
> instance Divide2 Main.Rational where
>   divide2 (Rational a b) = Rational a (2 * b)
> instance Increment Integer where
>   inc x = x + 1

我可以定义一个类型类或另一个类型类的实例工作的东西

> div4 x = divide2 (divide2 x)
> add2 :: Increment c => c -> c
> add2 = inc . inc

然后我想采用这两种数据类型的并集...所以显而易见的事情是使用一个

> data Number = Rat Main.Rational | Int Integer

现在。。。在我的场景中,作用于此联合的函数存在于一个不同的模块中(二进制文件,我不熟悉 Haskells 二进制文件)

但是数据类型本身是在另一个中定义的

很明显,我可以定义一些(原则上)可以在这个联合上"工作"的函数,例如作用于增量实例值的函数。还有一些没有,例如 Divide2 中的一个

那么我如何编写一个函数,针对这种可区分的联合,将函数应用于联合中的值,它将在增量上编译函数,但不在 Divide2 上的函数上编译......我会去这里,然后脸上平淡无奇。

> apply (Rat r) f = f r
> apply (Int i) f = f i

.

• Couldn't match expected type ‘Main.Rational’
with actual type ‘Integer’
• In the first argument of ‘f’, namely ‘i’
In the expression: f i
In an equation for ‘apply’: apply (Int i) f = f i
|
86 | > apply (Int i) f = f i    |                       ^
Failed, no modules loaded.

正如预期的那样,推论说它必须是理性的,因为第一次调用,但它是一个整数......

但"显然"...如果我能让哈斯克尔怀疑不相信......就像某种宏...然后函数

> apply (Int 1) add2

确实有意义,而且,对于我想选择的数字的任何值都是有意义的。

所以显而易见的事情是使 Number 成为每个成员所在的类型类集交集中的任何内容的成员。

> instance Increment Number where
>   inc (Rat r) = inc (Rat r)
>   inc (Int i) = inc (Int i)

然后 GHC 实现"应用"本身...我也可以通过一些显式字典传递将此解决方案映射回其他语言......但我有成百上千个微小的类型类(我什至可能还必须考虑它们的所有组合)。

所以我真的想知道是否有某种类型魔法(存在主义?rankn?),这意味着我可以针对 Number 编写"apply",而无需诉诸一些依赖类型魔法,或者必须在可区分的联合上实现类型类的实例。

附言我可以做有限的依赖型魔法...但这是最后的手段,

编辑。。。

包含 Number 上定义的函数的代码当然可以匹配可区分的值,但如果它们匹配,那么每当联合扩展时,它们将无法编译(好吧,它们不必单独匹配每个大小写,但除非它们这样做,否则它们无法提取包装的值来应用函数,因为它不知道类型)

嗯......写下来看起来像表达式问题,实际上是表达式问题......我知道很多解决方案...我只是通常不喜欢他们中的任何一个...让我使用类型类来敲定规范的 Haskell 解决方案。

您只能接受使用Increment方法的函数(并且不使用任何非Incremental 功能),如下所示:

{-# LANGUAGE RankNTypes #-}
apply :: (forall a. Increment a => a -> a) -> Number -> Number
apply f (Rat r) = Rat (f r)
apply f (Int i) = Int (f i)

如果您愿意,现在可以将add2传递给apply

> apply add2 (Rat (Rational 3 4))
Rat (Rational 11 4)

在这种特定情况下,实现apply与为Number本身提供Increment实例完全相同:

instance Increment Number where
inc (Rat r) = Rat (inc r)
inc (Int i) = Int (inc i)

。现在您甚至不需要中介apply功能即可应用add2

> add2 (Rat (Rational 3 4))
Rat (Rational 11 4)

但这是一个非常特殊的情况;仅仅为Number实现适当的类并不总是那么容易,你需要求助于我们在apply中使用的更高等级的类型。

所以这是表达式问题,所以类型类解决了这个特定情况。

你把你想要通用的函数放在一些尚未定义的类型宇宙中

> class Add2 a where 
>   add2' :: a -> a
> newtype Number' a = Number' a
> instance (Increment a) => Add2 (Number' a) where
>   add2' (Number' x) = Number' $ inc (inc x)
> three = add2 (Int 1)

然后,使任何类型在类型类方面具有所需的前提条件,都位于广义"函数"的类型类中。

然后,实现新的"Number"数据类型,并在有意义的地方创建它们的实例。

最新更新