为什么我不能使用类型 `Show a => [Something -> a]`?
Why can't I use the type `Show a => [Something -> a]`?
我有一个记录类型说
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
谁能帮我理解一下?还有允许这样做的变通方法或类型系统扩展吗?
Haskell 中列表的所有元素必须具有相同类型,因此包含一个 Int
和一个 String
的列表根本不存在。可以在 GHC 中使用存在类型来解决这个问题,但你可能不应该这样做(这种使用存在类型被广泛认为是一种反模式,而且它往往不会表现得非常好)。另一种选择是从列表切换到元组,并使用 lens
包中的一些奇怪的东西来映射两个部分。它甚至可能有效。
类型签名并没有表达您的想法。
这似乎是一个普遍的误解。考虑函数
foo :: Show a => Rec -> a
人们经常认为这意味着“foo
可以 return 它想要的任何类型,只要该类型支持 Show
”。 没有。
实际上的意思是foo
必须能够return任何可能的类型,因为调用者 可以选择 return 类型。
稍加思考就会发现 foo
实际上是不存在的。无法将 Rec
变成 任何可能存在的类型 。做不到。
人们经常尝试做类似 Show a => [a]
的事情来表示 "a list of mixed types but they all have Show
"。那显然是行不通的;这种类型实际上意味着列表元素可以是任何类型,但它们仍然必须相同。
您尝试做的事情似乎很合理。不幸的是,我认为你的第一个例子已经尽可能接近了。您可以 尝试使用元组和透镜来解决这个问题。您可以 尝试改用模板Haskell。但除非你有很多领域,否则可能不值得付出努力。
您实际想要的类型不是:
Show a => [Rec -> a]
任何带有未绑定类型变量的类型声明都有一个隐式 forall
。以上相当于:
forall a. Show a => [Rec -> a]
这不是您想要的,因为 a
必须专门针对整个列表的单一类型。 (由调用者选择 any 一种类型,正如 MathematicalOrchid 指出的那样。)因为您希望列表中每个元素的 a
能够以不同方式实例化...你真正要找的是一种存在主义类型。
[exists a. Show a => Rec -> a]
您想要一种 Haskell 不太支持的子类型形式。 GHC 完全不支持上述语法。您可以使用新类型来完成此操作:
{-# 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 函数实例;它不必动态查找。
如果您每次手动写出 show
,就像在您的原始示例中一样,那么 Haskell 可以在编译时为每次调用 show
确定正确的实例。
至于存在...这取决于实现,但据推测,编译器无法确定静态使用哪个实例,因此将改用动态查找。
我想推荐一些非常简单的东西:
recToString r = intercalate "\t" [s recNumber, s recName]
where s f = show (f r)
我有一个记录类型说
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
谁能帮我理解一下?还有允许这样做的变通方法或类型系统扩展吗?
Haskell 中列表的所有元素必须具有相同类型,因此包含一个 Int
和一个 String
的列表根本不存在。可以在 GHC 中使用存在类型来解决这个问题,但你可能不应该这样做(这种使用存在类型被广泛认为是一种反模式,而且它往往不会表现得非常好)。另一种选择是从列表切换到元组,并使用 lens
包中的一些奇怪的东西来映射两个部分。它甚至可能有效。
类型签名并没有表达您的想法。
这似乎是一个普遍的误解。考虑函数
foo :: Show a => Rec -> a
人们经常认为这意味着“foo
可以 return 它想要的任何类型,只要该类型支持 Show
”。 没有。
实际上的意思是foo
必须能够return任何可能的类型,因为调用者 可以选择 return 类型。
稍加思考就会发现 foo
实际上是不存在的。无法将 Rec
变成 任何可能存在的类型 。做不到。
人们经常尝试做类似 Show a => [a]
的事情来表示 "a list of mixed types but they all have Show
"。那显然是行不通的;这种类型实际上意味着列表元素可以是任何类型,但它们仍然必须相同。
您尝试做的事情似乎很合理。不幸的是,我认为你的第一个例子已经尽可能接近了。您可以 尝试使用元组和透镜来解决这个问题。您可以 尝试改用模板Haskell。但除非你有很多领域,否则可能不值得付出努力。
您实际想要的类型不是:
Show a => [Rec -> a]
任何带有未绑定类型变量的类型声明都有一个隐式 forall
。以上相当于:
forall a. Show a => [Rec -> a]
这不是您想要的,因为 a
必须专门针对整个列表的单一类型。 (由调用者选择 any 一种类型,正如 MathematicalOrchid 指出的那样。)因为您希望列表中每个元素的 a
能够以不同方式实例化...你真正要找的是一种存在主义类型。
[exists a. Show a => Rec -> a]
您想要一种 Haskell 不太支持的子类型形式。 GHC 完全不支持上述语法。您可以使用新类型来完成此操作:
{-# 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 函数实例;它不必动态查找。
如果您每次手动写出 show
,就像在您的原始示例中一样,那么 Haskell 可以在编译时为每次调用 show
确定正确的实例。
至于存在...这取决于实现,但据推测,编译器无法确定静态使用哪个实例,因此将改用动态查找。
我想推荐一些非常简单的东西:
recToString r = intercalate "\t" [s recNumber, s recName]
where s f = show (f r)