我如何避免在Haskell中的TypeClasses上的通用函数中的歧义类型错误



我正在尝试为打字机编写通用函数:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
module Foo1 where
import Data.Foldable
class Foo t where
  type FooPType t :: *              -- Base type.
  type FooFType t :: * -> *         -- Container type.
  defPs :: FooFType t (FooPType t)  -- Initialized container.
-- An attempt at a universal testing function valid for all types, t,
-- of class Foo for which `FooFType t` is a foldable functor.
tst :: forall t.
  ( Foo t
  , Functor  (FooFType t)
  , Foldable (FooFType t)
  ) => FooPType t
tst = (head . toList) defPs

,但是,我从GHC(8.0.2(遇到了此错误:

Foo1.hs:30:23: error:
    • Couldn't match type ‘FooPType t0’ with ‘FooPType t’
      Expected type: FooFType t0 (FooPType t)
        Actual type: FooFType t0 (FooPType t0)
      NB: ‘FooPType’ is a type function, and may not be injective
      The type variable ‘t0’ is ambiguous
    • In the first argument of ‘head . toList’, namely ‘defPs’
      In the expression: (head . toList) defPs
      In an equation for ‘tst’: tst = (head . toList) defPs
    • Relevant bindings include
        tst :: FooPType t (bound at Foo1.hs:30:1)

稍微戳了一下,我发现有些人通过将"类型"更改为"数据"来解决这个问题,但这对我不起作用。(我将其更改为 fooftype 。我是否应该为 fooptype ?for这两个更改它?(

d'OH!在发布之前,我应该尝试自己回答最后一个问题。果然,更改此代码:

  type FooPType t :: *              -- Base type.

阅读:

  data FooPType t :: *              -- Base type.

摆脱了我的编译错误。

谁能解释为什么这种变化有效?

这是解决方案,根据@htnw的提示,回复:在" defps"之后添加" @t":

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Foo1 where
import Data.Foldable
class Foo t where
  type FooPType t :: *              -- Base type.
  type FooFType t :: * -> *         -- Container type.
  defPs :: FooFType t (FooPType t)  -- Initialized container.
-- An attempt at a universal testing function valid for all types of class Foo.
tst :: forall t.
  ( Foo t
  , Functor  (FooFType t)
  , Foldable (FooFType t)
  ) => FooPType t
tst = (head . toList) $ defPs @t

上面的代码在GHC 8.0.2下编译带有错误的代码:

Davids-Air-2:so_noninjective_type_funcs dbanas$ stack ghc -- -c Foo1.hs 
Davids-Air-2:so_noninjective_type_funcs dbanas$ 
tst :: forall t. _ => FooPType t
tst = head $ toList _ -- want f (FooPType t)
defPs :: FooFType u (FooPType u)
tst = head $ toList defPs
-- try to unify (f (FooPType t)) with
--   (FooFType u (FooPType u))
-- Assume that f is injective: f x ~ g y iff f ~ g and x ~ y
-- GHC assumes this because it doesn't allow you to abstract over non-injective
--   type constructors anyway.
-- try to unify f with FooFType u; OK
-- try to unify FooPType t with FooPType u; oops

如果FooPTypedata family,则FooPType x ~ FooPType y表示x ~ y,因为data family s是注入的。在这里,它只是一个type family,这意味着编译器无法推断您要针对t调用defPs。例如,您可以将FooPType u ~ FooPType t添加到test的上下文中,现在ut都是有效的类型参数到defPs

test :: forall t u.
        (  Foo t, Foo u
         , Foldable (FooFType t), Foldable (FooFType u)
         , FooPType t ~ FooPType u
        ) => FooPType u
test = head $ toList defPs -- uh oh; which one?
instance Foo Bool where
  type FooPType Bool = Int
  type FooFType Bool = []
  defPs = [1]
instance Foo Int where
  type FooPType Int = Int
  type FooFType Int = []
  defPs = [3]
test @Bool @Int -- 1 or 3?

有趣的是,即使是类型的签名也无法在这里节省您。似乎有必要使用类型应用程序:

{-# LANGUAGE ExplicitForAll, ScopedTypeVariables, TypeApplications, ... #-}
test :: forall t. (Foo t, Foldable (FooFType t)) => FooPType t
test = head $ toList $ defPs @t

最新更新