我想使用异构列表列表。为此,我定义了一个简单的代数数据类型:
data T = LInt [Int]
| LChar [Char]
deriving (Eq, Ord, Show)
所以我现在可以这样:
x = [LInt [1, 2, 3], LChar "test"]
我的问题:这种类型可以成为Functor的一个实例吗?这会很有用;例如,选择x中列表的元素,如
fmap (!!2) LChar "test" => 's'
在我看来,这是不可能的。除了问题的动机之外,我相信答案可以帮助我更好地理解代数数据类型。
答案 0 :(得分:9)
不,它不能成为Functor
。
这不能成为函数的第一个原因是仿函数必须具有* -> *
种类。类似于类型的类型,你甚至可以使用:kind <type>
在GHCi中检查它们。例如:
> :kind Int
Int :: *
> :kind []
[] :: * -> *
> :kind Num
Num :: * -> Constraint
> :kind Maybe
Maybe :: * -> *
> :kind Either
Either :: * -> * -> *
> :kind Functor
Functor :: (* -> *) -> Constraint
*
基本上是指完全应用的类型,例如Int
,Char
,[String]
等,* -> *
之类的意思是类型需要单一类型*
以返回新类型*
。约束也有种类,即它们在完全应用时返回Constraint
种类。
您的类型有*
种类,与* -> *
的参数所需的Functor
不匹配。为了使它成为Functor
,它需要接受一个类型变量。在这里添加一个类型变量没有多大意义,但你可以有
data T a = LInt [a] | LChar [a]
但这不是很有用,我们现在无法强制LInt
仅包含Int
和LChar
仅包含Char
。更糟糕的是,看看我们fmap
的类型
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
但你想做的事情就像是
myfmap :: (a -> b) -> (f a -> b)
请注意返回类型是b
而不是f b
。 fmap
函数仅转换容器内的值,它不从所述容器中提取值。
可以编写一个使用-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] -> b
和f :: [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
种。函子必须是具有单个类型参数的类型构造函数,例如Maybe
,Either a
,fmap
。这反映在fmap :: Functor f => (a -> b) -> f a -> f b
:
f
所以a
需要一个类型参数T
。您的类型{{1}}没有此类参数。