我一直在做一些构建自己的自定义前奏的工作,我想构建一个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)
所以我的问题当然是为什么会这样做? 我做错了什么,更改如何解决它?
使用Fundepa b -> c
,您说函数类型(a
(和参数类型(b
(决定了结果类型(c
(。将其更改为a -> b c
意味着函数类型决定了参数和结果类型,这就是您想要的:如果a
替换为a' -> b'
,b
替换为a'
,c
替换为b'
,那么函数类型确实包含消除歧义所需的信息。
您需要在ghci中打开FlexibleContexts
。 GHCI 不会仅仅因为在已加载的文件中使用了扩展而以交互方式启用扩展名。 有时有点尴尬,但我明白为什么。 您可以一次加载多个文件,每个文件可以指定不同的扩展名 - 您可能不希望所有扩展名的并集,因为某些扩展名不能与其他扩展名很好地配合使用。
您可以在命令行使用-XFlexibleContexts
调用 ghci,也可以在 ghci 中使用:set -XFlexibleContexts
来启用它。
至于为什么首先要求您启用它? Haskell对上下文的形式有非常严格的规则。 限制性规则来自历史上的担忧,即放松这些规则可能会增加显着的实施复杂性。 事实证明,放松它们并没有什么坏处,这就是该扩展的作用。 它已经到了我什至不记得它应该保护什么的地步 - 如果编译器询问,我会打开该扩展。