了解Haskell的演员

时间:2017-05-15 08:52:00

标签: haskell

假设我们已导入Data.Typeable,其中包含cast :: (Typeable a, Typeable b) -> a -> Maybe b

考虑一下

> cast 'n' :: Maybe Char
Just 'n'

> cast 7 :: Maybe Char
Nothing

我理解上述情况,但这似乎是一个微不足道的例子。它没有透露为什么有人需要使用cast运算符(据我所见)。

问题:是否有使用cast的示例,其中真正"更改"从一种类型到另一种类型的值的类型?我能想到的最接近的例子(实际上在GHCi中不起作用)会将7的类型从Integer更改为Double

> cast 7 :: Maybe Double
Just '7' -- this doesn't work, as 7 is typed as a Integer above; instead GHCi returns Nothing

3 个答案:

答案 0 :(得分:7)

  

是否有一个使用强制转换的例子,它真正将某种类型的值从一种类型“改变”到另一种类型?

简单的答案是否定的。但是,可能存在一种你不知道你的类型的情况,因为它被量词“存在”“隐藏”了。这是一个例子:

{-# LANGUAGE ExistentialQuantification #-}

import Data.Typeable
import Data.Maybe

data Foo = forall a . Typeable a => Foo a

main = print . catMaybes . map (\(Foo x) -> cast x :: Maybe Bool) $ xs
  where
    xs = [Foo True, Foo 'a', Foo "str", Foo False]

输出将是:

[True,False]

答案 1 :(得分:5)

Typeable课程的目的是让您在不了解确切类型时使用数据。 cast运算符的目的是检查某些数据是否具有特定类型,如果是,则允许您将其作为该类型使用。这完全是为了对数据的类型签名做些什么。实际的根本不会改变。

如果您想更改某些数据的,那不是演员,那就是转换。有各种各样的功能可以做到这一点。例如,fromIntegralIntegral的实例变为其他内容。 (例如,IntDouble。)然而, cast 不是什么。

答案 2 :(得分:4)

假设您想要在所有值上实现一个充当身份函数的函数,除了它应该作为后继函数的整数。

在像Python这样的无类型语言中,这很简单:

>>> def f(x):
...   if type(x) == int:
...     return x+1
...   else:
...     return x
... 
>>> f(42)
43
>>> f("qwerty")
'qwerty'

即使在Java中,这也是可行的,即使它需要一些演员:

static <A> A f(A a) {
    if (a instanceof Integer)
        return (A) (Integer) ((Integer) a + 1);
    else
        return a;
}

public static void main (String[] args) throws java.lang.Exception
{
    System.out.println(">> " + f(42));
    System.out.println(">> " + f("qwerty"));
}

/*  Output:
>> 43
>> qwerty
*/

Haskell怎么样?

在Haskell中,这是不可能完成的。类型forall a. a -> a的任何函数都必须无法终止,或者表现为身份。这是与该类型相关联的free theorem的直接结果。

但是,如果我们可以在类型Typeable上添加a约束,那么就有可能:

f :: forall a. Typeable a => a -> a
f x = case cast x :: Maybe Int of
   Nothing -> x
   Just n  -> case cast (n+1) :: Maybe a of
      Nothing -> x
      Just y -> y

第一个casta转换为Int,第二个casta转换回Int

上面的代码非常难看,因为我们知道第二次演员不会失败,所以没有真正需要第二次演员。我们可以使用eqT和类型相等的GADT改进上面的代码:

f :: forall a. Typeable a => a -> a
f x = case eqT :: Maybe (a :~: Int) of
   Nothing   -> x
   Just Refl -> x+1

基本上,eqT告诉我们两个(Typeable)类型是否相等。更好的是,在与Just Refl匹配之后,编译器也知道它们是相等的,并且允许我们使用Int值而不是a值,可以互换,并且不使用强制转换。

确实,感谢GADT,:~:eqT,现在cast的大多数用途都是 过时。使用cast

可以轻松实现eqT本身
cast :: forall a. (Typeable a, Typeable b) => a -> Maybe b
cast x = case eqT :: Maybe (a :~: b) of
   Nothing   -> Nothing
   Just Refl -> Just x

结论:在Haskell中,我们得到了两全其美。我们确实有多态类型的参数保证(自由定理)。我们还可以打破这些参数保证,并使用“ad hoc”多态,代价是额外的Typeable约束。