haskell 多参数类型类的单参数表示

zujrkrfu  于 9个月前  发布在  其他
关注(0)|答案(3)|浏览(103)

出于某种原因(目标计算机上奇怪的Hugs安装),我想要Haskell98兼容的代码,这意味着没有公共语言扩展。一个特别的烦恼是缺少多参数类型类,例如在我的例子中:

class Semiring d where
  zero :: d
  one :: d
  plus :: d -> d -> d
  times :: d -> d -> d

class Monoid e where
  mzero :: e
  mplus :: e -> e -> e

class (Semiring d, Monoid e) => Module d e where
  mtimes :: d -> e -> e

我有一种感觉,这个问题特别是可以以某种方式解决,也许通过定义一些帮助类型类并以某种方式“打包”有关类型的信息,但我无法弄清楚。
我的直觉有道理吗?I am reading多参数类型类在编写Haskell 98标准时就已经被广泛使用了,当时有没有办法绕过这个限制?

hl0ma9xz

hl0ma9xz1#

我不确定它可以在H98中完全完成。但是这里有一个黑客,它有一个非常容易支持的扩展,FlexibleInstances,它可能在Hugs中可用(尽管可能不是在这个名字下)。

{-# Language FlexibleInstances #-}
data ModuleDict d e = ModuleDict { mtimes :: d -> e -> e }
class Default a where def :: a
instance Default (ModuleDict Int Int) where def = ModuleDict (+)

example :: Int
example = mtimes (def :: ModuleDict Int Int) 3 4

-- more generalizably:
-- example :: Default (ModuleDict Int Int) => Int
-- this additionally requires FlexibleContexts, but that should be supported
-- everywhere that FlexibleInstances is, as they're kinda useless without
-- each other

虽然它不允许你直接调用mtimes,但它实际上与你所提议的多参数类型类的使用方式并没有那么大的不同。也就是说,在许多情况下,MPTC需要mtimes上的类型注解来确定要使用哪个示例;可以认为def上的类型注解本质上是类似的。当然,一个缺点是,如果上下文确实修复了类型,那么使用MPTC可以删除类型注解,但是使用这种解决方案,您需要保留def
这也不能真正让您表示只有Semiring s和Monoid s可以相关的约束,因此您将在许多类型签名中复制这些约束。你可以通过扩展字典来避免这种情况:

data SemiringDict d = SemiringDict { zero, one :: d, plus, times :: d -> d -> d }
data ModuleDict d e = ModuleDict { semiringDict :: SemiringDict d, mtimes :: d -> e -> e }

你的类型签名可以更短,但是“类方法”的 * 用法 * 会明显更冗长。

ff29svar

ff29svar2#

Oleg Kiselyov在2007年写了一个演示,Haskell只有一个类型类:
[I]f我们删除了类型类声明和标准类型类,使语言只剩下一个单一的、固定的、预定义的类型类和一个单一的方法,没有失去表达能力。我们仍然可以编写所有Haskell 98类型类编程习惯用法,包括构造函数类,以及多参数类型类,甚至一些函数依赖。
简单地说,他引入了一个带有函数依赖和单个方法的双参数类C

class C l t | l -> t where ac :: l -> t

这将一个“标签”l与它的类型t联系起来,例如:

data Add a

instance C (Add Int) (Int -> Int -> Int) where
  ac _ x y = x + y

(+) :: forall a. (C (Add a) (a -> a -> a)) => a -> a -> a
(+) = ac (undefined :: Add a)

这并不是严格意义上的H98,因为它使用了一个限定作用域的类型变量,但我相信这些在Hugs中很早就实现了。
现在,由于多参数类实际上是一组类型元组,我们可以将其重新表述为单参数类,其元素是元组,但失去了函数依赖性。

class Overload a where overload :: a -> a

data Add a

instance Overload (Add Int, Int -> Int -> Int) where
  overload (label, _) = (label, \x y -> x + y)

apply :: (Overload (label, signature)) => label -> signature
apply label = f
  where
    (_, f) = overload (label, f)

(+) :: forall a. (Overload (Add a, a -> a -> a)) => a -> a -> a
(+) = apply (undefined :: Add a)

在现代的Haskell中,你可能希望使用代理,但我保留了undefined/bottom的使用,这既是出于旧Haskell的精神,也是为了与前面的例子相似。
当然,缺少fundep意味着编译器在很多情况下无法推断出明确的类型,因为它无法确定每个标签(如Add Int)只有一个对应的签名Int -> Int -> Int。所以这可能需要一个类型注解。另一方面,它赠款了您一定的灵活性,因为如果您想构造子类型或重叠示例之类的东西,那么从技术上讲,您可以为同一术语给予多种类型。如果你想避免的只是多参数类,那么你可以通过不限制自己只使用一个类来避免很多歧义。
您还可以使用等式约束手动编码函数依赖关系(如果可用):

-- |
-- Compare:
--
-- > class Widen a b | a -> b where widen :: a -> b
-- > instance Widen Bool Int where widen = fromEnum

instance (b ~ Int) => Overload (Widen Bool b, Bool -> Int) where
    overload (label, _) = (label, fromEnum)

更多的细节和示例在文章和附带的代码中。

uujelgoq

uujelgoq3#

(目标计算机上奇怪的拥抱安装)我想要Haskell 98兼容的代码,
Hugs有HugsMode:菜单文件->选项->允许拥抱/GHC扩展[单选按钮]

或者像命令行中的here-98选项那样。(令人困惑的是,+98表示H98模式;所以-98表示 * 不是 * H98模式--即Hugs模式。)从文档页面,请按照第7节链接查看完整的概要。
我想不出为什么你的安装是如此奇怪,你不能运行拥抱模式。
我曾经得到一个版本的拥抱一个非常旧的型号的iPad。它忘记打开HugsMode来构建它。(iPad从此寿终正寝;也就是说,刘翔也死了)。
还有对拥抱无礼的人拥抱(模式)是一个很长的路要走在H2010(除了不支持模式后卫)。而且有一个不太好的记录系统--不像GHC。所以我用拥抱比GHC更严重的工作。With a little bit of tweaking,你甚至可以说服拥抱一个记录代数。

相关问题