一种模拟没有类型运算符的Union类型的方法

问题描述 投票:0回答:2

我在使用类型操作符之前已经这样做了,但是我想排除那些,因为我希望能够用更小的锤子来做它,因为我实际上想用另一种语言来做,而且我不太确定类型操作符做什么我想要的。

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

> 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中的一个

那么我如何编写一个函数,针对这个有区别的联合,将一个函数应用于union中的值,这将为Increment上的函数编译,但是不要在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.

正如预期的那样,推断说它因为第一次调用而成为一个Rational,但它是一个整数......

但“明确”......如果我能让哈斯克尔怀疑不相信......就像某种宏......那么功能

> 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上定义的函数的代码当然可以匹配被识别的值,但是如果它们匹配,那么每当扩展union时,它们将无法编译(好吧,它们不必单独匹配每个案例,但除非它们do,他们无法提取包装值来应用函数,因为它不知道类型)

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

haskell
2个回答
1
投票

您只能接受使用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与为Increment本身提供Number实例完全相同:

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中使用的更高级别的类型。


1
投票

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

你可以使用你想要的一些尚未定义的类型范围的函数

> 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)

然后根据类型类生成任何类型的类型,为类广义的“函数”生成类型类。

然后,您可以实现新的“数字”数据类型,并在它们有意义的地方创建它们的实例。

© www.soinside.com 2019 - 2024. All rights reserved.