为什么扩展我的函数依赖项会消除非类型变量参数?

时间:2018-06-22 03:50:09

标签: haskell ghc functional-dependencies

我一直在做一些构建自己的自定义序曲的工作,我想构建一个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)

所以我的问题当然是为什么这项工作有效?我在做什么错,更改是如何解决的?

2 个答案:

答案 0 :(得分:6)

使用Fundep a b -> c,您是说函数类型(a)和参数类型(b)确定了结果类型(c)。将其更改为a -> b c意味着函数类型将确定参数和结果类型,这正是您想要的:如果aa' -> b'替换,b被{{ 1}}和a'c,那么实际上函数类型就是包含消除歧义所需的信息的东西。

答案 1 :(得分:4)

您需要在ghci中打开FlexibleContexts。 ghci不会以交互方式启用扩展,只是因为扩展已在您已加载的文件中使用过。有时候有点尴尬,但我知道为什么。您可以一次加载多个文件,每个文件可以指定不同的扩展名-并且您可能不希望所有扩展名的并集,因为某些扩展名不能与其他扩展名配合使用。

您可以在命令行中使用-XFlexibleContexts来调用ghci,也可以将:set -XFlexibleContexts与ghci一起使用来启用它。

至于为什么首先要求您启用它? Haskell对上下文形式有非常严格的规定。限制性规则源于历史上的担心,即担心放松规则可能会增加实施的复杂性。事实证明,放松它们没有任何危害,这就是扩展的作用。到现在为止,我什至都不记得它应该防止什么-如果编译器要求,我打开该扩展名。