Haskell中的“forall”量化用法示例

6tdlim6h  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(72)

我正在通过fp-course学习haskell。当我为StateT实现Functor时,发生了一个错误。

-- | Implement the `Functor` instance for @StateT s k@ given a @Functor k@.
--
-- >>> runStateT ((+1) <$> (pure 2) :: StateT Int List Int) 0
-- [(3,0)]
instance Functor k => Functor (StateT s k) where
  (<$>) ::
    forall k a b s. (a -> b)
    -> StateT s k a
    -> StateT s k b
  (<$>) f st1 = StateT $ \s -> mapFunc <$> (runStateT st1 s)
          where
            mapFunc :: (a, s) -> (b, s)
            mapFunc = \(a, s) -> (f a, s)

误差

src/Course/StateT.hs:48:32: error:
    • Could not deduce (Functor k1) arising from a use of ‘<$>’
      from the context: Functor k
        bound by the instance declaration at src/Course/StateT.hs:43:10-42
      Possible fix:
        add (Functor k1) to the context of
          the type signature for:
            (<$>) :: (a -> b) -> StateT s1 k1 a -> StateT s1 k1 b
    • In the expression: mapFunc <$> (runStateT st1 s)
      In the second argument of ‘($)’, namely
        ‘\ s -> mapFunc <$> (runStateT st1 s)’
      In the expression: StateT $ \ s -> mapFunc <$> (runStateT st1 s)
Failed, modules loaded: Course.Applicative, Course.Core, Course.ExactlyOne, Course.Functor, Course.List, Course.Monad, Course.Optional, Course.State.

为什么会这样?如何解决?

p3rjfoxz

p3rjfoxz1#

当使用ScopedTypeVariables扩展时,forall关键字声明了新的类型变量,这些变量隐藏了任何具有相同名称的现有类型变量的定义,因此您的示例等效于:

instance Functor k => Functor (StateT s k) where
  (<$>) ::
    forall k1 a b s1. (a -> b)
    -> StateT s1 k1 a
    -> StateT s1 k1 b
  (<$>) f st1 = StateT $ \s1 -> mapFunc <$> (runStateT st1 s1)
          where
            mapFunc :: (a, s1) -> (b, s1)
            mapFunc = \(a, s1) -> (f a, s1)

换句话说,即使你应该为函子类型StateT s k定义运算符(<$>)(因为这是你正在编写的示例),你也在为不同的类型StateT s1 k1定义。
不过,这没关系,因为GHC将允许您给予“错误”类型StateT s1 k1的示例定义,如果它可以与所需类型StateT s k统一的话。通过将特定类型s与任意类型s1统一,并将特定类型k与任意类型k1统一,GHC可以做到这一点。
但是,这是假设您的定义类型检查。您在定义右侧使用的(<$>)具有(a -> b) -> k1 a -> k1 b类型,因此需要k1Functor示例。但是,即使GHC从示例头知道Functor k示例,它以前也从未听说过k1类型,而且您从未在类型签名中说过它有一个Functor示例。如果你写的是:

instance Functor k => Functor (StateT s k) where
  (<$>) ::
    forall k1 a b s1. (Functor k1) => (a -> b)
--                    ^^^^^^^^^^^^^^^--- add this
    -> StateT s1 k1 a
    -> StateT s1 k1 b
  (<$>) f st1 = StateT $ \s1 -> mapFunc <$> (runStateT st1 s1)
          where
            mapFunc :: (a, s1) -> (b, s1)
            mapFunc = \(a, s1) -> (f a, s1)    instance Functor k =>

一切都将类型检查正常。
为了清楚起见,在这个定义中发生的事情是,你已经告诉GHC你将为来自示例头的特定sk的类型StateT s k定义(<$>),但是你却为通用s1k1定义了一个定义,与示例头中的sk无关,并表明只要这个新的任意类型k1有一个Functor示例,您的定义就可以工作。GHC接受此定义并验证具有任意s1k1的类型StateT s1 k1可以与来自示例头的具有特定skStateT s k统一。既然它可以,它就接受你的定义。
对于StateT s k类型,这是一种非常迂回的(<$>)定义方式。相反,您应该只使用示例头中的特定sk类型变量,并且通过在forallNOT将它们声明为新变量来实现这一点:

instance Functor k => Functor (StateT s k) where
  (<$>) ::
    forall a b. (Functor k) => (a -> b)
--                       ^      ^----^--- these are fresh type vars
--                       |
--                       `--- but this is `k` from instance head
    -> StateT s k a
--            ^^^ ^-- again, fresh a
--             |
--             `-- but `s` and `k` from instance head
    -> StateT s k b
--            ^^^ ^-- fresh b
--             |
--             `-- `s` and `k` from instance head
  (<$>) f st1 = StateT $ \s -> mapFunc <$> (runStateT st1 s)
          where
            mapFunc :: (a, s) -> (b, s)
--                     ^^^^^^^^^^^^^^^^
--                        No forall here, so `a` and `b` come
--                        from `forall a b` in previous type
--                        signature, while `s` comes from the
--                        instance head
            mapFunc = \(a, s) -> (f a, s)
rbpvctlc

rbpvctlc2#

你的实现很好。但是(<$>)并不是Functor合约的一部分。它是在类外部定义的别名。Functor合约需要fmap。如果我们将您的实现更改为使用fmap并删除无关的类型注解(instance声明不需要),则可以获得工作代码,而无需更改函数的实际主体。

instance Functor k => Functor (StateT s k) where
    fmap f st1 = StateT $ \s -> mapFunc <$> (runStateT st1 s)
        where mapFunc = \(a, s) -> (f a, s)

相关问题