在Haskell中键入System

akn*_*320 11 haskell

我无法理解为什么以下代码会导致错误.如果第二个字段Foo更改为type 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]'
Run Code Online (Sandbox Code Playgroud)

为什么show无法接受不同类型的参数?当然,以下工作:

Prelude> show $ foo_a x
"2"
Prelude> show $ foo_b x
"3.4"
Run Code Online (Sandbox Code Playgroud)

此外,鉴于这不起作用,应用于show数据类型的各个字段的推荐方法是什么?

谢谢.

Hel*_*ira 8

问题是Haskell中的列表是同类的(所有元素都具有相同的类型).并且[foo_a, foo_b]您正在尝试创建一个包含两种不同类型的列表:Foo -> IntFoo -> 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"
Run Code Online (Sandbox Code Playgroud)

((\fn -> fn x)可以写成($ x))


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

{-# 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
Run Code Online (Sandbox Code Playgroud)

然后,你可以这样做:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"
Run Code Online (Sandbox Code Playgroud)

[更新]

我正在玩这个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"
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们可以做到:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"
Run Code Online (Sandbox Code Playgroud)

似乎我们必须编写更多代码来实现相同的结果,但它实际上为我们提供了更大的灵活性.虽然存在主义类型只关注于显示领域String,但在这里我们不仅限于此.如果现在我们想要查看所有字段Int(在Float我们想要舍入值的情况下),该怎么办?

getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $                get_a dyn
                       <|> (round .) <$> get_b dyn
                       <|> error "Type mismatch"
Run Code Online (Sandbox Code Playgroud)

现在我们可以做到:

*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]
Run Code Online (Sandbox Code Playgroud)


luq*_*qui 7

这是Hindley-Milner型系统的限制.只有let(和等效地,在where顶层和顶层)的定义可以是多态的.特别是,函数的参数不能是多态的.fn必须有类型Foo -> IntFoo -> Float- 没有Foo -> (Int or Float)Foo -> Showable类型.

如果必须,你可以定义一个Showable类型,它被称为存在类型,但是你必须给typechecker一些帮助以便使用它,并且在大多数代码中没有使用这个想法,因为它的不便超过了它的用处,如果没有它我们通常可以毫无困难地表达我们想要的东西.

  • 大多数情况下都是正确的,但有一个更简单的解释:`[foo_a,foo_b]`是一个`List a`,因此所有元素必须具有相同的类型.但是`foo_a :: Foo-> Int`不能与`foo_b :: Foo-> Float`统一.与`["String",1,True]`基本相同. (10认同)