探索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 c
与ConstraintKinds
一起编译,但我不允许instance IDrawable Object where i = IDrawable . objectDraw
犯错误
'i' is not a (visible) method of class 'IDrawable`
有没有办法声明IDrawable c = Interface IDrawable' c
以便它可以实例化?
这纯粹是出于学术兴趣,我不建议任何人在实际应用程序中使用此模式,我只是想知道如果不应用TemplateHaskell
或{{1 }}
答案 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已经触及了这一点,我在那里发表了评论;然而,由于我最后也写了一个答案,我想我也会在这里添加它。)