数据构造函数的部分应用

Partial application of data constructor

我不明白为什么在Haskell从第一性原理编程中进行以下练习"works":

type Subject = String
type Verb = String
type Object = String

data Sentence =
    Sentence Subject Verb Object
    deriving (Eq, Show)

s1 = Sentence "dogs" "drool"
s2 = Sentence "Julie" "loves" "dogs"

将其加载到 ghci 中表明它可以进行类型检查,但为什么 s1 的定义甚至有意义?我对 Haskell 还是很陌生,所以一开始我以为这是因为在 s1 中 Haskell 隐含地让 Object 字符串为空。但是后来...

*Main> s1

<interactive>:13:1:
    No instance for (Show (Object -> Sentence))
      arising from a use of `print'
    Possible fix:
      add an instance declaration for (Show (Object -> Sentence))
    In a stmt of an interactive GHCi command: print it

我仍在学习如何正确解读这些错误消息,所以请多多包涵。但是有人可以解释一下 No instance for (Show (Object -> Sentence)) 是什么意思吗?更具体地说,在 s1 中遗漏 Object 字符串是如何导致这个 (Object -> Sentence) 的?

我敢肯定这很简单,但我认为这本书到现在还没有让我理解这一点...

but why is it that the definition of s1 even makes sense?

正如@Alec 所提到的,它被称为 currying。查看正在发生的事情的一种方法是让 GHCI 告诉您 s1 的类型是什么:

ghci> :t s1
s1 :: Object -> Sentence

所以 s1 是一个将 Object 转换为 Sentence 的函数。另一种思考方式是从定义开始:

s1 = Sentence "dogs" "drool"

并使用等式推理将两边应用于一个值 x:

s1 x = Sentence "dogs" "drool" x

因此,当您调用 s1 x 时,它与调用 Sentence 时的前两个函数参数硬编码为 "dogs""drool" 以及 [=23] 是一样的=] 成为 Sentence 函数的第三个参数。

can someone explain what "No instance for (Show (Object -> Sentence))" means?

当你在 GHCI 中评估某些东西时,它基本上与要求 Haskell 到 print 相同。也就是说,

ghci> 3+4

实际上等同于:

ghci> print (3+4)

(此规则不适用于像 getLine 甚至 print 本身这样的 IO 操作。在那些情况下 Haskell 只是 运行 的 IO 操作.)

为了 print 某些东西,类型必须有一个 Show 实例。 但是正如我们在上面看到的,s1 是一个类型为 Object -> Sentence 的函数,并且没有为函数预定义的 Show 实例。

请注意,Sentence 值有一个 Show 实例,因为您要求 GHC 使用 deriving (Eq, Show) 派生一个。所以当你在 GHCI 提示符下输入:

ghci> Sentence "Julie" "loves" "dogs"

你回来了:

Sentence "Julie" "loves" "dogs"

因为你真的要求 GHCI 运行 print (Sentence "Julie" "loves" "dogs")

注意 print 本身定义为 (link):

print x = putStrLn (show x)

和对 show 的调用是为什么值需要为其定义 Show 实例才能打印的原因。

No instance for (Show (Object -> Sentence))
  arising from a use of `print'
Possible fix:
  add an instance declaration for (Show (Object -> Sentence))
In a stmt of an interactive GHCi command: print it

补充@ErikR 的回答:您可能想知道为什么 GHC 没有对显示函数的内置支持,即与整数和字符串不同,函数没有 Show 的实例typeclass(这些术语将在本书后面进行深入解释,所以如果您不了解什么是类型类和实例,请不要担心),除非您自己明确定义它。作为一个正在学习 Haskell 并且来自面向对象背景的人,我发现通过将类型类视为 Java 类接口更容易获得对类型类的直觉。

那么,为什么函数没有 Show 实例? Haskell wiki 提供了两个答案:

1.Practically,GHC 不跟踪变量名,即以下对于编译器是相同的:

addOne num = num + 1
f x = x + 1
f y = y + 1  

此外,还可以优化功能,例如以下可能具有等效表示

f x = x - x + x
f x = x

2.Theoretically,函数由其图形定义,即(输入,输出)对的集合。例如对于

f x = x + x

对是 (1,2)、(2,4) 等。 因此,具有相同图形的函数在 GHC 方面是相同的,例如

f x = x + x
g y = 2 * y

但您会认为 show fshow g 不同,尤其是当您使用重要的变量名称而不是 x 和 y 时。

也就是说,您可以使用仅显示函数类型的 pragma(GHC 编译器的扩展,包含一些超出 Haskell 语言标准的功能),如 [=32= 中所述]:

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Typeable

instance (Typeable a, Typeable b) => Show (a->b) where
  show _ = show $ typeOf (undefined :: a -> b)

这会让你

> s1
[Char] -> Sentence

因为 ObjectString 的别名(使用 type 关键字,String 本身就是 [Char] 的别名)。 如果你想明确地看到主体和客体之间的区别,你可以将 Object 转换为具有数据类型构造函数的类型 MkObject:

newtype Object = MkObject String deriving (Eq, Show)

s1 = Sentence "dogs" "drool"
s2 = Sentence "Julie" "loves" (MkObject "dogs")

瞧瞧

> s1
Object -> Sentence