问题 在Haskell中键入System


我无法理解为什么以下代码会导致错误。如果第二场 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 到数据类型的各个领域?

谢谢。


12214
2017-08-14 05:59


起源

不幸的是,没有简单的方法可以做你想要的,因为不同的字段将有不同的类型,因此无法使用列表和处理 map。可能有一些紧凑的解决方案使用Template Haskell或一些更高级的扩展... - Bakuriu


答案:


问题是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)


另一种可能性是创建一种数据类型来统一你想要放入列表中的几种类型,模仿a 异构集合。在你的情况下,它可能是这样的:

{-# 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]

8
2017-08-14 09:05





这是Hindley-Milner型系统的限制。只有定义 let (等同于,在 where 并且在顶层)可以是多态的。特别是,函数的参数不能是多态的。 fn 必须要有类型 Foo -> Int 要么 Foo -> Float  - 没有 Foo -> (Int or Float) 要么 Foo -> Showable 类型。

如果必须,您可以定义一个Showable类型,称为 存在类型但是你必须给typechecker一些帮助才能使用它,并且在大多数代码中都没有使用这个想法,因为它的不便之处超过了它的用处,而且我们通常没有它就能表达我们想要的东西。


8
2017-08-14 06:26



大多数都是正确的,但有一个更简单的解释: [foo_a, foo_b] 是一个 List a,因此所有元素必须具有相同的类型。但 foo_a::Foo->Int 无法统一 foo_b::Foo->Float 。基本上与之相同 ["String", 1, True]。 - deniss