我一直在做一些构建自己的自定义序曲的工作,我想构建一个Callable
类型的类,该类将为函数以外的类型实现函数应用程序(($)
)。因此,我使用多参数类型类构建了一个类型类:
{-# Language MultiParamTypeClasses #-}
import Prelude ()
class Callable a b c where
($) :: a -> b -> c
现在,我继续将函数设为Callable
类型类的实例,这需要我启用灵活的实例。
{-# Language MultiParamTypeClasses, FlexibleInstances #-}
import Prelude ()
id :: a -> a
id x = x
class Callable a b c where
($) :: a -> b -> c
instance Callable (a -> b) a b where
($) = id
这很好,现在我可以在函数上使用($)
。因此,对我来说,下一个合乎逻辑的步骤是也要实现函数组合((.)
)。经过一番摆弄之后,我意识到,要这样做,我需要使Callable
在功能上具有依赖性,因此我打开了功能依赖性。
{-# Language MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}
import Prelude ()
id :: a -> a
id x = x
class Callable a b c | a b -> c where
($) :: a -> b -> c
instance Callable (a -> b) a b where
($) = id
(.) :: (Callable f1 intype intermediate, Callable f2 intermediate outtype) => f2 -> f1 -> intype -> outtype
(.) a b c = a $ (b $ c)
这确实可以编译。实际上,如果我可以使用我的(.)
来创建函数。但是,如果我尝试使用新功能(至少以我尝试使用的任何方式),它将无法进行类型检查,并带有相当隐秘的错误。
~/p/dynamo > ghci callable.hs
GHCi, version 8.4.2: http://www.haskell.org/ghc/ :? for help
[1 of 1] Compiling Main ( callable.hs, interpreted )
Ok, one module loaded.
*Main> :t (id).(id)
(id).(id)
:: (Callable (a1 -> a1) c e, Callable (a2 -> a2) e d) => c -> d
*Main> ((id).(id)) $ ()
<interactive>:2:1: error:
• Non type-variable argument
in the constraint: Callable (c1 -> d) () c2
(Use FlexibleContexts to permit this)
• When checking the inferred type
it :: forall c1 d c2 a1 e a2.
(Callable (c1 -> d) () c2, Callable (a1 -> a1) c1 e,
Callable (a2 -> a2) e d) =>
c2
*Main>
我真的无法理解此错误试图传达的内容。但这表明我打开了灵活的上下文,所以我想我会旋转一下,如果它解决了很大的问题,并且如果它改变了错误,我可能会遇到问题。但是,如果我打开灵活的上下文,该错误不会改变,实际上它甚至仍然表明我打开了灵活的上下文。
这时我想我会读书。我读了一些关于Non type-variable argument
的问题,但是我并没有真正感到自己对自己的特定问题有任何了解。正是在这一点上,我内心深处的某些想法使我想到也使b
成为功能依赖项。我不知道为什么,但这实际上可以解决我的问题。工作代码如下所示:
{-# Language MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}
import Prelude ()
id :: a -> a
id x = x
class Callable a b c | a -> b c where
($) :: a -> b -> c
instance Callable (a -> b) a b where
($) = id
(.) :: (Callable f1 intype intermediate, Callable f2 intermediate outtype) => f2 -> f1 -> intype -> outtype
(.) a b c = a $ (b $ c)
所以我的问题当然是为什么这项工作有效?我在做什么错,更改是如何解决的?
答案 0 :(得分:6)
使用Fundep a b -> c
,您是说函数类型(a
)和参数类型(b
)确定了结果类型(c
)。将其更改为a -> b c
意味着函数类型将确定参数和结果类型,这正是您想要的:如果a
被a' -> b'
替换,b
被{{ 1}}和a'
与c
,那么实际上函数类型就是包含消除歧义所需的信息的东西。
答案 1 :(得分:4)
您需要在ghci中打开FlexibleContexts
。 ghci不会以交互方式启用扩展,只是因为扩展已在您已加载的文件中使用过。有时候有点尴尬,但我知道为什么。您可以一次加载多个文件,每个文件可以指定不同的扩展名-并且您可能不希望所有扩展名的并集,因为某些扩展名不能与其他扩展名配合使用。
您可以在命令行中使用-XFlexibleContexts
来调用ghci,也可以将:set -XFlexibleContexts
与ghci一起使用来启用它。
至于为什么首先要求您启用它? Haskell对上下文形式有非常严格的规定。限制性规则源于历史上的担心,即担心放松规则可能会增加实施的复杂性。事实证明,放松它们没有任何危害,这就是扩展的作用。到现在为止,我什至都不记得它应该防止什么-如果编译器要求,我打开该扩展名。