定义部分应用的类型类

时间:2015-03-13 18:33:22

标签: haskell typeclass

探索typeclasses are essentially C++ abstract classes without nested inheritance的想法,我编写了类型类

class Interface i c where
    i :: c -> i

instance Interface i i where i = id

infixl 1 #
(#) :: Interface i c => c -> (i -> r) -> r
c # f = f $ i c

使用类似

的界面
data IDrawable' = IDrawable { draw :: IO () }

我想要像

这样的东西
type IDrawable c = Interface IDrawable' c

这样我才能做到

data Object = Object { objectDraw :: IO () }
data Person = Person { personDraw :: IO () }

instance IDrawable Object where i = IDrawable . objectDraw
instance IDrawable Person where i = IDrawable . personDraw

虽然type IDrawable cConstraintKinds一起编译,但我不允许instance IDrawable Object where i = IDrawable . objectDraw犯错误

'i' is not a (visible) method of class 'IDrawable`

有没有办法声明IDrawable c = Interface IDrawable' c以便它可以实例化?

这纯粹是出于学术兴趣,我不建议任何人在实际应用程序中使用此模式,我只是想知道如果不应用TemplateHaskell或{{1 }}

2 个答案:

答案 0 :(得分:1)

您可以声明一个没有实例的“部分”类:

class Interface IDrawable' c => IDrawable c

instance Interface IDrawable' Object where i = IDrawable . objectDraw
instance Interface IDrawable' Person where i = IDrawable . personDraw

或者,可以使用约束同义词:

type IDrawable c = Interface IDrawable' c

虽然优雅的解决方案可能更合适,因为IDrawable类具有适当的* -> Constraint类,而类型同义词除非完全应用,否则无法使用。这可能是相关的,因为数据定义(和类型族以及几乎所有类型级别的hackery)只能使用正确的类型构造函数。

答案 1 :(得分:1)

不,这是不可能的(截至7.8.3,我认为也是7.10);它是GHC bug #7543。这不是一个非常被贩运的错误;显然至少有一些人愿意写这种东西(例如,你,Edward Kmett),但大多数人都没有注意到这一点。更改跟踪器上记录的此行为没有任何进展。

至于为什么你不能,让我解释Simon Peyton-Jones对bug追踪器的解释。问题是类型检查实例有两个部分:首先,GHC必须查找方法名称(此处为i)的来源;第二,GHC必须扩展类型同义词。由于GHC的两个不同组件在本期中执行了这两个步骤,因此不能支持约束同义词实例; GHC无法确定查找i需要查找的课程。


这是一个错误的另一个原因 - 根据对András Kovács's answer的评论,我发现这一点的原因是当前的行为并不像“它不起作用”那么简单。相反,它尝试工作,但你不能声明任何方法......但你可以声明一个无方法的实例!在GHCi:

GHCi, version 7.8.3: http://www.haskell.org/ghc/  :? for help
...
Prelude> :set -XMultiParamTypeClasses -XFlexibleInstances -XConstraintKinds
Prelude> class Interface i c where i :: c -> i
Prelude> instance Interface i i where i = id
Prelude> let (#) :: Interface i c => c -> (i -> r) -> r ; c # f = f $ i c ; infixl 1 #
Prelude> data IDrawable' = IDrawable { draw :: IO () }
Prelude> type IDrawable = Interface IDrawable'
Prelude> instance IDrawable () where i _ = IDrawable $ return ()

<interactive>:8:29:
    ‘i’ is not a (visible) method of class ‘IDrawable’
Prelude> ()#draw

<interactive>:9:3:
    No instance for (Interface IDrawable' ()) arising from a use of ‘#’
    In the expression: () # draw
    In an equation for ‘it’: it = () # draw
Prelude> instance IDrawable () where {}

<interactive>:10:10: Warning:
    No explicit implementation for
      ‘i’
    In the instance declaration for ‘Interface IDrawable' ()’
Prelude> ()#draw
*** Exception: <interactive>:10:10-21: No instance nor default method for class operation Ghci1.i

换句话说:

instance IDrawable () where i _ = IDrawable $ return ()

失败了,但是

instance IDrawable () where {}

成功!很明显,根据所需的行为,检查需要放松或收紧: - )


P.S。:还有一件事:你应该尽可能地减少类型同义词。这就是我将IDrawable更改为

的原因
type IDrawable = Interface IDrawable'

并在上面的c代码中删除了GHCi参数。这样做的好处是,由于无法部分应用类型同义词,因此您无法将IDrawable的版本作为参数传递给任何内容;但是,完全减少版本的版本可以传递到期望某种* -> Constraint的任何地方。

András Kovács's answer已经触及了这一点,我在那里发表了评论;然而,由于我最后也写了一个答案,我想我也会在这里添加它。)