为什么我不能使用“Show a =>”类型? [东西 - >一]`?

时间:2015-02-23 17:03:40

标签: haskell

我有记录类型说

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 

有人可以帮助我理解这个吗?还有解决方法或类型系统扩展允许这个吗?

4 个答案:

答案 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包中的一些奇怪的东西来映射这两个部分。它甚至可能有效。