我有记录类型说
data Rec {
recNumber :: Int
, recName :: String
-- more fields of various types
}
我想为Rec编写一个toString
函数:
recToString :: Rec -> String
recToString r = intercalate "\t" $ map ($ r) fields
where fields = [show . recNumber, show . recName]
这很有效。 fields
的类型为[Rec -> String]
。但我很懒,我更喜欢写作
recToString r = intercalate "\t" $ map (\f -> show $ f r) fields
where fields = [recNumber, recName]
但这不起作用。直觉上我会说fields
的类型为Show a => [Rec -> a]
,这应该没问题。但是Haskell不允许这样做。
我想了解这里发生了什么。如果我在第一种情况下说我获得了一个函数列表,那么show的2个实例实际上不是同一个函数,但Haskell能够确定哪个是编译时哪个(这就是为什么它没问题)。
[show . recNumber, show . recName]
^-- This is show in instance Show Number
^-- This is show in instance Show String
在第二种情况下,我在代码中只有show
的一个字面用法,而且必须引用多个实例,而不是在编译时确定的?
map (\f -> show $ f r) fields
^-- Must be both instances at the same time
有人可以帮助我理解这个吗?还有解决方法或类型系统扩展允许这个吗?
答案 0 :(得分:15)
类型签名没有说明你的想法。
这似乎是一种常见的误解。考虑函数
foo :: Show a => Rec -> a
人们似乎经常认为这意味着“foo
可以返回它想要的任何类型,只要该类型支持Show
”。 没有。
实际意味着foo
必须能够返回任何可能的类型,因为来电者可以选择返回类型应该是什么。
一会儿'思想会发现foo
实际上不存在。无法将Rec
转换为可能存在的任何可能类型。它无法完成。
人们经常尝试执行Show a => [a]
之类的操作来表示“混合类型列表,但它们都有Show
”。这显然不起作用;这种类型实际上意味着列表元素可以是任何类型,但它们仍然必须完全相同。
你想要做的事似乎足够合理。不幸的是,我认为你的第一个例子就是尽可能接近。你可以尝试使用元组和镜头来解决这个问题。你可以尝试使用Template Haskell。但除非你有很多领域,否则它可能不值得努力。
答案 1 :(得分:7)
您真正想要的类型不是:
Show a => [Rec -> a]
具有未绑定类型变量的任何类型声明都具有隐式forall
。以上相当于:
forall a. Show a => [Rec -> a]
这不是你想要的,因为a
必须专门针对整个列表的单一类型。 (由调用者,他们选择的任何一种类型,如MathematicalOrchid所指出的那样。)因为您希望列表中每个元素的a
能够以不同方式实例化...你实际上寻求的是存在主义类型。
[exists a. Show a => Rec -> a]
你希望Haskell不能很好地支持一种形式的子类型。 GHC根本不支持上述语法。您可以使用newtypes来完成此任务:
{-# LANGUAGE ExistentialQuantification #-}
newtype Showy = forall a. Show a => Showy a
fields :: [Rec -> Showy]
fields = [Showy . recNumber, Showy . recName]
但不幸的是,这与直接转换为字符串一样乏味,不是吗?
我不相信镜头能够解决Haskell类型系统的这个特殊弱点:
recToString :: Rec -> String
recToString r = intercalate "\t" $ toListOf (each . to fieldShown) fields
where fields = (recNumber, recName)
fieldShown f = show (f r)
-- error: Couldn't match type Int with [Char]
假设字段 do 具有相同的类型:
fields = [recNumber, recNumber]
然后它工作,Haskell计算出在编译时使用哪个show function实例;它不必动态查找。
如果您每次手动写出show
,就像在原始示例中一样,那么Haskell可以在编译时为show
的每次调用确定正确的实例。
至于存在性......它取决于实现,但可能是编译器无法确定静态使用哪个实例,因此将使用动态查找。
答案 2 :(得分:4)
我想建议一些非常简单的事情:
recToString r = intercalate "\t" [s recNumber, s recName]
where s f = show (f r)
答案 3 :(得分:2)
Haskell中列表的所有元素必须具有相同的类型,因此包含一个Int
和一个String
的列表根本不存在。可以使用存在类型在GHC中解决这个问题,但是你可能不应该(这种存在的使用被广泛认为是一种反模式,并且它不会表现得非常好)。另一种选择是从列表切换到元组,并使用lens
包中的一些奇怪的东西来映射这两个部分。它甚至可能有效。