我无法理解为什么以下代码会导致错误。如果Foo
的第二个字段更改为Int
类型,则代码运行时没有错误。
Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> (show . fn) x) [foo_a, foo_b]
<interactive>:4:46:
Couldn't match type `Float' with `Int'
Expected type: Foo -> Int
Actual type: Foo -> Float
In the expression: foo_b
In the second argument of `map', namely `[foo_a, foo_b]'
In the second argument of `($)', namely
`map (\ fn -> (show . fn) x) [foo_a, foo_b]'
为什么show
无法接受不同类型的参数?当然,以下工作如下:
Prelude> show $ foo_a x
"2"
Prelude> show $ foo_b x
"3.4"
另外,鉴于这不起作用,将show
应用于数据类型的各个字段的推荐方法是什么?
感谢。
答案 0 :(得分:8)
这是Hindley-Milner型系统的限制。只有let
中的定义(等效地,在where
和顶层)中的定义才是多态的。特别是,函数的参数不能是多态的。 fn
必须包含Foo -> Int
或Foo -> Float
类型 - 没有Foo -> (Int or Float)
或Foo -> Showable
类型。
如果必须,您可以定义一个可显示的类型,称为existential type,但您必须为typechecker提供一些帮助以便使用它,并且在大多数代码中,该想法未被使用,因为它的不便之处超过了它的用处,我们通常可以毫无困难地表达我们想要的东西。
答案 1 :(得分:8)
问题是Haskell中的列表是同类的(所有元素都具有相同的类型)。在[foo_a, foo_b]
中,您尝试创建一个包含两种不同类型的列表:Foo -> Int
和Foo -> Float
。
一种解决方案是移动列表中的show
并创建Foo -> String
列表:
Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> fn x) [show . foo_a, show . foo_b]
"2 3.4"
((\fn -> fn x)
可以写成($ x)
)
另一种可能性是创建一种数据类型,以统一您想要放入列表中的几种类型,模仿heterogeneous collection。在你的情况下,它可能是这样的:
{-# LANGUAGE ExistentialQuantification #-}
data Showable b = forall a . Show a => MkShowable (b -> a)
pack :: Show a => (b -> a) -> Showable b
pack = MkShowable
unpack :: Showable b -> b -> String
unpack (MkShowable f) = show . f
然后,你可以这样做:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"
<强> [更新] 强>
我正在玩Data.Dynamic
,它似乎比我在上面创建的存在类型更有希望。
让我们从:
开始{-# LANGUAGE DeriveDataTypeable #-}
import Control.Applicative
import Data.Dynamic
import Data.Maybe
data Foo = Foo {foo_a :: Int, foo_b :: Float}
deriving Typeable
get_a :: Dynamic -> Maybe (Foo -> Int)
get_a = fromDynamic
get_b :: Dynamic -> Maybe (Foo -> Float)
get_b = fromDynamic
getAsString :: Dynamic -> (Foo -> String)
getAsString dyn = fromJust $ (show .) <$> get_a dyn
<|> (show .) <$> get_b dyn
<|> error "Type mismatch"
在这种情况下,我们可以这样做:
*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"
似乎我们必须编写更多代码来实现相同的结果,但实际上它给了我们更大的灵活性。虽然存在主义类型仅侧重于将字段显示为String
,但在此我们并不仅限于此。如果现在我们想要将所有字段视为Int
(在Float
的情况下我们想要对值进行舍入),该怎么办?
getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $ get_a dyn
<|> (round .) <$> get_b dyn
<|> error "Type mismatch"
现在我们可以做到:
*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]