【问题标题】:Why does adding an as-pattern to a working function cause compilation errors?为什么将 as-pattern 添加到工作函数会导致编译错误?
【发布时间】:2012-08-21 13:45:18
【问题描述】:

Here'sEither a 的标准 Functor 实例:

instance Functor (Either a) where
        fmap _ (Left x) = Left x
        fmap f (Right y) = Right (f y)

在加载到 GHCi 时添加 as-pattern 会导致编译错误:

instance Functor (Either a) where
        fmap _ z@(Left x) = z          -- <-- here's the as-pattern
        fmap f (Right y) = Right (f y)

Couldn't match expected type `b' against inferred type `a1'
  `b' is a rigid type variable bound by
      the type signature for `fmap' at <no location info>
  `a1' is a rigid type variable bound by
       the type signature for `fmap' at <no location info>
  Expected type: Either a b
  Inferred type: Either a a1
In the expression: z
In the definition of `fmap': fmap _ (z@(Left x)) = z

为什么这不起作用?

【问题讨论】:

  • Either x aEither x b 都有名为 L x 的元素,所以 fmap f (L x) = L x 有效。但是左边的a@(L x) 表示a 被标识为Either x a 类型的东西。但是右边需要的是 Either x b 类型的东西

标签: haskell types as-pattern


【解决方案1】:

fmap 具有签名(a -&gt; b) -&gt; f a -&gt; f b,即它必须允许ab 不同。在您的实现中,ab 只能相同,因为它返回作为参数传递的相同内容。所以 GHC 抱怨。

【讨论】:

  • 从初学者的角度来看,标准实例也是returns the same thing that was passed as an argument。差异有些微妙——至少对我而言。
  • @MattFenwick 微妙的是左边的Left 和右边的Left 不是同一个东西。右边那个更多态!您可能想尝试编写data Phantom a = Phantom 并比较f a@Phantom = ag Phantom = Phantom 的类型。
【解决方案2】:

我最好的猜测是这会失败,因为z 代表等式两边的不同类型:

  • 整体类型为fmap :: (a -&gt; b) -&gt; Either t a -&gt; Either t b

  • 在左侧,z :: Either t a

  • 在右侧,z :: Either t b

似乎Left x 允许在同一个方程中有多个不同的类型,但z 不允许。

这个实现也失败了,显然是出于同样的原因:

instance Functor (Either a) where
        fmap f (Right y) = Right (f y)
        fmap _ z = z          

Couldn't match expected type `b' against inferred type `a1'
  `b' is a rigid type variable bound by
      the type signature for `fmap' at <no location info>
  `a1' is a rigid type variable bound by
       the type signature for `fmap' at <no location info>
  Expected type: Either a b
  Inferred type: Either a a1
In the expression: z
In the definition of `fmap': fmap _ z = z

【讨论】:

  • 没什么奇怪的,因为z 的类型固定为Either t a。另一方面,Left :: forall a b. a -&gt; Either a bLeft x :: forall b. Either t b,它允许你选择任何你想要的 b
  • @Vitus 好点,我已经习惯了 Haskell 的隐式 forall 和类型推断,以至于我通常看不到这些差异......
【解决方案3】:

如果你将fmap的签名专门化为Either l,你会得到:

fmap :: (a -> b) -> Either l a -> Either l b

这意味着您在 case 语句左侧进行模式匹配的 Left r 必须具有 Either l a 类型。但是,您不能按原样返回它,因为您必须返回一个Either l b。这需要将左侧值重新包装在新的 Left 中,以便编译器可以推断它正在返回一个新生成的 Either,而 Right 值可能具有不同的类型。

【讨论】:

    【解决方案4】:

    对于Either a 实例,fmap 具有以下类型:

    (i -> j) -> Either a i -> Either a j
    

    在这个等式中:

    fmap _ (Left x) = Left x
    

    已知第二个参数是Either a i 类型并且匹配Left x 模式。我们把x取出来,对它应用Left得到fmap的结果。

    诀窍在于等式左侧的Left 与右侧的Left 不同!在 LHS Left 上是这个构造函数:

    Left :: a -> Either a i
    

    而在 RHS 上,Left 是这个构造函数:

    Left :: a -> Either a j
    

    模式中使用的 RHS Left 不会匹配 fmap 的第二个参数 Either a i 的类型,并且 LHS Left 不会构造所需类型的值 fmap的结果Either a j

    因此,从语义上讲,无法对这两种事物使用相同的Left;您必须构造一个新的Left x :: Either a j,其中包含Left x :: Either a i 中的x在操作上,Haskell 实现可能会或可能不会相同地表示这两个术语,并且可能会或可能不会共享具有不同类型的两个值的单个内存表示,并且可能会或可能不会足够聪明,可以优化一个新值的构造,该值将与现有的另一个值相同地表示。但是这些实现问题与程序的含义是不同的含义,类型检查器的角色纯粹是与含义有关。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-31
      • 1970-01-01
      • 2011-06-09
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 2012-10-05
      相关资源
      最近更新 更多