Control.Lens中的"Illegal polymorphic or qualified type"是指非法的多态或限定类型。

11

我正在使用 Control.Lens。我正在编写的实际函数相当复杂,但是为了这个问题,我将其简化为一个最小的失败示例:

import Control.Lens    

exampleFunc :: Lens s t a b -> String
exampleFunc _ = "Example"

这段代码编译失败,会出现以下错误信息:

Illegal polymorphic or qualified type: Lens s t a b
Perhaps you intended to use -XRankNTypes or -XRank2Types
In the type signature for `exampleFunc':
  exampleFunc :: Lens s t a b -> String

为什么这是非法的?它看起来与以下内容非常相似,后者确实可以编译:
import Data.Maybe

exampleFunc' :: Maybe (s, t, a, b) -> String
exampleFunc' _ = "Example"

所以我认为区别在于 Lens 的定义。但是,Lens 类型中的什么使得 exampleFunc 的类型不合法呢?我有一种隐隐的感觉,这与 Lens 定义中的 Functor 限定有关,但我可能错了。参考此处 Lens 的定义:

type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t

那么,我需要在exampleFunc的定义中以某种方式满足Functor限制吗?如果是这样,怎么做呢?我没有看到在我的类型签名中声明此约束的机会。或者我可能走错了路,我的问题与Functor限制无关。

我已阅读了所有我能找到有关“非法多态等”错误消息的Stack Overflow问题。也许这是因为我不熟悉Haskell,但我看不出这些问题中任何一个适用于我的当前情况。

我也没有找到任何关于该错误消息在一般情况下表示什么的文档。


RankNTypes是编译器建议开启的扩展之一。对于许多其他扩展来说也是可以的,但对于OverlappingInstancesIncoherentInstances可能不太适用。 - leftaroundabout
2个回答

9

Lens使用二阶类型,您需要将其放在箭头左侧,以使用任何类似这样的镜头类型,因此,您必须使其合法才能发出类似以下内容的语言

(forall a. foo) -> bar

你也可以使用以下方法完成,

与此相同

{-# LANGUAGE RankNTypes #-} -- Rank2Types is a synonym for RankNTypes

在你的文件顶部加入这个语句。如果没有它,使用镜头类型的同义词甚至是非法的,因为它们使用了必须启用的语言部分。


这不是负位置的意思。例如,在 (a -> r) -> r 中,a 出现在正位置。 - Ben Millwood
@BenMillwood 这是一个简化版本,是的,负数和正数位置的行为类似于乘法,即负数位置的负数位置是正数位置。但对于这个实例,我觉得简化的描述已经足够了。 - daniel gratzer

7
exampleFunc 无法编译,因为Lens类型同义词是多态的,并且出现在签名中被称为“负位置”,也就是在 -> 左侧。
即使没有使用RankNTypes,您仍然可以在类型签名中使用Lens。这个类型检查通过了。
import Control.Lens

lensy :: Lens' (a,b) a 
lensy = _1

但是这样无法通过类型检查:

oops :: Lens' (a,b) a -> Int
oops = const 5 

为什么?同样的原因,没有使用RankNTypes也无法通过类型检查:
{-# LANGUAGE ExplicitForAll #-}

fails :: (forall a. a -> Int) -> Int
fails = undefined

这里的forall在负位置,仅涵盖a -> Int。它是fails的实现,而不是fails的调用者,后者选择a的类型。调用者必须提供适用于所有a的参数函数。这个特性需要RankNTypes扩展
forall覆盖整个签名(例如在孤立定义Lens时)时,不需要RankNTypes。此代码段通过类型检查:
{-# LANGUAGE ExplicitForAll #-}

typechecks :: forall a. (a -> Int) -> Int
typechecks = undefined

但是这个函数与之前的不同,因为这里是调用者选择a的类型。他可以传递一个只针对特定a工作的参数函数。 exampleFunc'之所以有效,是因为当没有指定forall时,每个变量都有隐式的foralls,范围覆盖整个签名。
哈斯克尔邮件列表中的这个解释可能会有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接