简单代数数据类型的Functor实例

时间:2015-01-06 16:54:22

标签: haskell functor algebraic-data-types

我想使用异构列表列表。为此,我定义了一个简单的代数数据类型:

data T = LInt [Int]
       | LChar [Char]
       deriving (Eq, Ord, Show)

所以我现在可以这样:

x = [LInt [1, 2, 3], LChar "test"]

我的问题:这种类型可以成为Functor的一个实例吗?这会很有用;例如,选择x中列表的元素,如

fmap (!!2) LChar "test"  => 's'

在我看来,这是不可能的。除了问题的动机之外,我相信答案可以帮助我更好地理解代数数据类型。

2 个答案:

答案 0 :(得分:9)

简答:

不,它不能成为Functor

长答案:

这不能成为函数的第一个原因是仿函数必须具有* -> *种类。类似于类型的类型,你甚至可以使用:kind <type>在GHCi中检查它们。例如:

> :kind Int
Int :: *
> :kind []
[] :: * -> *
> :kind Num
Num :: * -> Constraint
> :kind Maybe
Maybe :: * -> *
> :kind Either
Either :: * -> * -> *
> :kind Functor
Functor :: (* -> *) -> Constraint

*基本上是指完全应用的类型,例如IntChar[String]等,* -> *之类的意思是类型需要单一类型*以返回新类型*。约束也有种类,即它们在完全应用时返回Constraint种类。

您的类型有*种类,与* -> *的参数所需的Functor不匹配。为了使它成为Functor,它需要接受一个类型变量。在这里添加一个类型变量没有多大意义,但你可以有

data T a = LInt [a] | LChar [a]

但这不是很有用,我们现在无法强制LInt仅包含IntLChar仅包含Char。更糟糕的是,看看我们fmap的类型

class Functor f where
    fmap :: (a -> b) -> (f a -> f b)

但你想做的事情就像是

myfmap :: (a -> b) -> (f a -> b)

请注意返回类型是b而不是f bfmap函数仅转换容器内的值,它不从所述容器中提取值。

可以编写一个使用-XGADTs约束的参数化类型,但是,你可以编写

data T a where
    LInt :: [Int] -> T Int
    LChar :: [Char] -> T Char

这样可以保证类型是理智的,但仍然无法将其变为Functor的实例(满足算子法则),它会阻止您创建异构列表( T Int /~ T Char)。

所以看起来Functor选项恰好是正确的。您可能会发现编写类似

的函数很有吸引力
tmap :: ([a] -> b) -> T -> b
tmap f (LInt x) = f x
tmap f (LChar x) = f x

但这也行不通。类型系统看到您试图说f :: [Int] -> bf :: [Char] -> b,这是无法统一的。您可以通过启用-XRankNTypes来执行此操作:

tmap :: (forall a. [a] -> b) -> T -> b
tmap f (LInt x) = f x
tmap f (LChar x) = f x

这确实允许你做类似

的事情
> tmap length (LInt [1, 2, 3])
3
> tmap length (LChar "test")
4

但它不会让你做到

> tmap (!! 2) (LChar "test")
Couldn't match type 'b' with 'a'
  because type variable 'a' would escape its scope
This (rigid, skolem) type variable is bound by
  a type expected by the context: [a] -> b
Expected type: [a] -> b
  Actual type: [a] -> a
...

这意味着类型a无法出现在输出类型b中的任何位置,因为传入的f必须适用于所有 a,它不适用于任何a

总之,无需进一步深入到类型系统疯狂中,你的类型就无法做到你想要的那样。您将不得不编写专门的函数来单独处理每个案例,这几乎是ADT的重点。编译器可以确保您实际处理每个案例,只要您远离返回undefined或调用error的函数,那么您的程序将是安全的。它可能没有你想要的那么灵活,但在安全方面它会坚如磐石。

答案 1 :(得分:4)

不,因为它没有合适的kind。函数* -> *应该有T*IO种。函子必须是具有单个类型参数的类型构造函数,例如MaybeEither afmap。这反映在fmap :: Functor f => (a -> b) -> f a -> f b

的类型中
f

所以a需要一个类型参数T。您的类型{{1}}没有此类参数。

相关问题