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

问题描述 投票:2回答:2

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

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

haskell ghc functional-dependencies
2个回答
6
投票

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


4
投票

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

您可以在命令行中用FlexibleContexts调用ghci,或使用ghci中的-XFlexibleContexts启用它。

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

© www.soinside.com 2019 - 2024. All rights reserved.