一种无需类型运算符即可模拟Union类型的方法

时间:2019-04-12 10:40:24

标签: haskell

我已经在使用类型运算符之前完成了此操作,但是我想排除那些,因为我希望能够用较小的锤子来完成,因为我实际上想用另一种语言来完成,而且我不太确定类型运算符完全可以满足我的要求。

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

> 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二进制文件不熟悉)

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

因此,很明显,我可以定义一些(原则上)可以在此联合上“起作用”的函数,例如作用于Increment实例值的函数....以及一些不起作用的函数,例如Divide2中的一个

那么我如何针对这个有区别的联合编写一个函数,将一个函数应用于联合中的值,该函数将针对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,因为它是第一个调用,但是它是Integer ...

但是“很明显” ...如果我可以让haskell怀疑置疑...像某种宏...然后使用功能

> apply (Int 1) add2

对我想选择的Number的任何值都有意义,而且有意义。

因此,显而易见的事情是使Number成为每个成员所在的类型类集合的交集中任何事物的成员。...

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

然后ghc自己实现“应用” ...我也可以通过一些显式的字典传递将该解决方案映射回其他语言...但是我有数百个(如果不是上千个)小类型类(我可能不得不还要考虑它们的所有组合)。

所以我真的很想知道是否存在某种类型魔术(现有的?rankn?),这意味着我可以针对Number编写“应用”,而无需求助于某些依赖类型魔术,或者必须在该类上实现类型类的实例。歧视工会。

P.S。我可以做有限的依赖类型魔术...但这是不得已的手段,

编辑...

包含在Number上定义的函数的代码当然可以匹配已区分的值,但是如果这样做,则只要扩展并集,它们都将无法编译(好吧,它们不必分别匹配每种情况,但是除非这样做,否则他们将无法提取包装的值来应用该函数,因为它不会知道类型)

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

2 个答案:

答案 0 :(得分: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)

,然后根据类型类创建任何占用所需先决条件的类型,并使用您的广义“函数”的类型类。

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

答案 1 :(得分:1)

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

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