1

I am sorry if this question seems ill thought-out, but I was wondering if it would be possible to define a consistent semantics for something like the following in Haskell:

derive Num String from Int where
    get = read
    set = show

derive Ord Bool from Integer where
    get = fromEnum
    set = toEnum

derive (Monad, Functor) Maybe from [] where
    get (Just x) = [x]
    get Nothing  = [ ]
    set [x]      = Just x
    set [ ]      = Nothing

I see no reason why not, and it seems like it would cut down on boilerplate in some situations but I don't know if (and if so, how easily) this could be implemented.

Edit:

My intention would be for e.g. the first example to be replaced with something like this:

instance Num String where
    get = read :: String -> Int
    set = show :: Int    -> String
    a + b = set $ get a + get b
    a * b = set $ get a * get b
    ...
4

1 回答 1

4

您在这里描述的本质上是通过同构定义类实例。这显然可以通过定义getandset函数来实现,尽管让我们称它们tofro更清楚一点。

toSI   :: String -> Integer
fromSI :: Integer -> String

instance Num String where
  a + b = fromSI $ (toSI a) + (toSI b)
  abs   = fromSI . abs . toSI
  ...

这些确实是很容易编写的定义,并且通过(to, fro)自动提升类实例化来减少样板文件可能有一些价值,但是这个系统必须仔细遵循许多规则,以免垃圾污染全局类型类实例空间。

特别是,我们可能会要求(to, fro)形成同构。这意味着两个方向的往返都是身份。换句话说,它意味着给定任何 integer n,我不应该以任何方式区分(froSI (toSI n))n嗯,一些像计算速度这样的东西被忽略了)。此外,对于任何字符串,我必须具有相同的属性stoSI (froSI s)必须与n.

第二个显然失败了,因为"I am not a number!"在往返中引发了错误。有很多原因导致在纯代码中抛出错误是危险的,而对于类型类,这种危险会继续存在并污染任何曾经导入您的代码的人的代码。

您可能会指出,这只是因为并非所有字符串都是有效数字。似乎fromSI . toSI总是麻烦,但toSI . fromSI应该工作。也许它只会影响实例化之类的事情instance Num String,如果我们改为使用我们的(toSI, froSI)配对来派生一些instanceInteger我们String会处于有利位置。也许。

让我们试试看。String是一个Monoid看起来像这样的实例

instance Monoid [a] where       -- if a ~ Char then this is String
  mempty = []
  mappend as bs = as ++ bs

如果我们mappend通过mappend = toSI . mappend . fromSI它实现给我们“连接整数”,比如

(1 <> 2) <> 3   ==   123
1 <> (2 <> 3)   ==   123
(0 <> 1) <> 1   ==   11
0 <> (1 <> 1)   ==   11

如果我们小心地定义"" -> 0而不是让它失败,那么我们也可以得到一个有用的mempty

mempty = toSI mempty

这似乎应该运作良好。它确实是MonoidInteger它的“单向同构”继承而来的String(想想为什么我必须在Integer这里使用,而不是Int)。更具体地说,我们无法通过从类型类中的函数构建的任何测试来区分toSI (fromSI n)n因此Monoid进行此映射“足够好”。

但随后我们又遇到了另一个问题。Integer已经有一个Monoid实例。它已经有大约 10 个,最受欢迎的是乘法和加法

instance Monoid Integer            instance Monoid Integer
  mempty  = 0                        mempty  = 1
  mappend = (+)                      mappend = (*)

因此,通过选择这些实例中的任何一个作为 Monoid的规范类型类实例,我们会丢失大量信息。更准确地说,它通过它IntegerMonoid“单向同构”(又名“retract”)与String但也通过它的剥离以仅具有加法,但也通过其剥离以仅具有乘法。

实际上,我们希望保留这些信息,这就是为什么Monoid包定义了类似的东西Sum,并且Product表明它Integer已经专门用于仅使用它的Addition属性。

归根结底,这正是您将类型类实例提升到同构之上的问题。通常,类型有很多可以以这种方式滥用的同构和缩回,并且很难有真正规范的、遵守法律的实例。当您找到一个时,通常值得将代码税明确地写出来,即使您最终使用同构来这样做。

当没有规范的选择时,您可以使用类似工具newtype和大量库来快速访问您的newtype层“下方”的内容,从通用GeneralizedNewtypeDeriving扩展到Control.Newtype或直接启发Iso的 ,auauf整个Wrapped, ala,alaf机制lens.

本质上,这种机制已经到位,可以更容易地丰富地讨论继承于各种同构的实例,尤其是那些由newtype.

于 2013-08-29T14:26:24.680 回答