Typecheck因明显相等的实例定义而失败

时间:2018-03-31 12:31:37

标签: haskell typechecking

给出以下类型定义

CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    cookieManager.setAcceptThirdPartyCookies(mWebview, true);
} else {
    cookieManager.setAcceptCookie(true);
}

newtype Constant a b = Constant { getConstant :: a } deriving (Eq, Show) Functor定义有效

instance

而明显等价的instance Functor (Constant a) where fmap _ (Constant x) = Constant x 定义

instance

因类型检查错误(摘录)而失败

instance Functor (Constant a) where
  fmap _ x = x

使用GHC 8.0.2版。

问题是,为什么这两个(显然是等效的)Expected type: Constant a b Actual type: Constant a a1 定义在类型检查方面表现不同。

3 个答案:

答案 0 :(得分:5)

如果我们给构造函数一个不同的名称,以便区分类型级别和值级别,可能会更清楚:

newtype Constant a b = ConstVal { getConstVal :: a }
  deriving (Eq, Show)

instance Functor (Constant a) where
  fmap _ (ConstVal x) = ConstVal x

现在,为什么你不能写fmap _ x = x

ConstVal是一个多态构造函数

ConstVal :: a -> Constant a b

...即

ConstVal :: ∀ a b . a -> Constant a b

虽然这个通用量词在Haskell中是可选的,但它实际上很重要。 ConstVal基本上有两个额外的类型级参数。换句话说,这不只是一个构造函数,而是一整个构造函数家族,比如

ConstValBoolBool :: Bool -> Constant Bool Bool
ConstValBoolInt  :: Bool -> Constant Bool Int
ConstValBoolChar :: Bool -> Constant Bool Char
...
ConstValCharBool :: Char -> Constant Char Bool
ConstValCharInt  :: Char -> Constant Char Int
ConstValCharChar :: Char -> Constant Char Char
...
...

所有这些实际上共享相同的值级别名称ConstVal,但对于类型系统,它们都是不同的。明确写出来,你有例如

fmapBoolStringInt :: (String -> Int) -> Constant Bool String -> Constant Bool Int
fmapBoolStringInt _ (ConstValBoolString x) = ConstValBoolInt x

这里显然双方的价值实际上并不相同,因此不能减少到fmapBoolStringInt _ x = x

答案 1 :(得分:2)

instance Functor (Constant a) fmap中有类型:

fmap :: (b -> c) -> Constant a b -> Constant a c

在表达式fmap _ x = x中,变量x必须具有不可能的不同类型:

fmap _ (x :: Constant a b) = (x :: Constant a c)

那个typechecker说你。

您可以使用unsafeCoerce,如下所示:

fmap _ x = unsafeCoerce x

fmap _ = unsafeCoerce

但这不是惯用的方式,因为即使您知道此时unsafeCoerce的应用是安全的,您也不能保证(通常情况下)一段时间之后。

但是,自base-4.7.0.0coerce在他的回答中写道)@Ben之后就有Qt/QML application的安全版本。

此外,我们可以预期编译器可以优化第一个定义,如第二个。

答案 2 :(得分:1)

fmap :: (a -> b) -> Constant t a -> Constant t b
fmap _ (Constant x) = Constant x

在这里,您解构类型Constant t a的值以获取类型x的值t,然后将其包装在Constant构造函数中以生成类型的值Constant t b所以你可以退货。

fmap :: (a -> b) -> Constant t a -> Constant t b
fmap _ x = x

您会收到Constant t a类型的值,并将其作为Constant t b类型的值返回。这不起作用。

我们可以想象它可以工作。由于Constant的第二个类型参数是一个幻像参数,因此这两种类型的内存中表示是相同的,因此对一种类型的内存中引用可以作为内存中的引用传递给另一种类型。类型,一切都会正常。

GHC运行时实际上实际上是这样做的 - x :: tConstant x :: Constant t aConstant x :: Constant t b在运行时都将引用同一块内存,并且没有工作通过这个功能。

但这是一个低级别的优化;从语义上讲,它们仍然是不同的类型,Haskell不允许将一种类型的值用作不同类型的值。

可以做的事情,因为Constantnewtype,使用coerce(请参阅https://hackage.haskell.org/package/base/docs/Data-Coerce.html)进行明确转换。这避免了你必须明确模式匹配和重建。转换在运行时将是无操作(由于内存表示相同)。所以:

fmap _ x = coerce x

甚至:

fmap _ = coerce