假设我们定义一个类型别名,说Message
为:
type alias Message a =
{ code : String
, body : a
}
然后将函数readMessage
定义为:
readMessage : Message () -> String
readMessage message =
...
上面的例子来自Elm教程和书中说:
此函数接收带有空体的Message。这与任何值都不一样,只是一个空值。
有人可以详细说明在上面的场景中发生了什么,以及编译器如何处理它。
除非你真的想看到它的内部编译器表示,否则我认为这里重要的是any value
和empty value
之间的区别。
Message a
是一个带参数的参数化类型。您可以将其作为模板阅读,例如只要小写a
出现在Message
的定义中,它将被替换为具体类型(String,Int等)。
所以这就是函数应该是什么样子,如果我们想要它与Message
身体采取String
:
readMessage : Message String -> String
readMessage message =
这里发生的是body
场的类型不再是a
而是String
(a
用String
代替):
{ code : String
, body : String
}
在Elm中没有任何东西(aka void
或unit
)的值被编码为()
。这就是为什么具有空体值的Message
看起来像这样:
{ code : String
, body : ()
}
但是,当我们根本不关心身体价值时,我们可以采取Message
与any
值:
readMessage : Message a -> String
readMessage message =
小写的a
可以是任何小写字符串,我们可以使它更像这样:
readMessage : Message any -> String
readMessage message =
但后来我们无法真正阅读邮件正文,因为我们不知道它的类型(因此我们不知道如何阅读它)。
希望有所帮助。
类型Message ()
是以下记录的别名:
{ code : String
, body : ()
}
其中()
类型表示没有任何项目的元组(也称为null元组)。这种类型只有一个值,它也写成()
。
现在,当我们想要省略记录中的某个字段时,我们不能只指定它 - 这会使编译器正确生气,另请参阅The Billion Dollar Mistake。我们需要告诉编译器可以省略该值。
我们可以做的一种方法是使用Maybe
类型但是如果我们制作一个消息列表,允许我们将主体包含在某些消息中并在其他消息中省略它。这可能不是我们想要的。
另一种方法是在问题中进行参数化Message
类型。这将允许我们在阅读消息时与String
身体发出消息,并在我们对身体不感兴趣时使用不同的身体类型。
在这种情况下,我们需要考虑身体类型应该是什么。虽然我们可以将空String
s用于带有遗漏体的消息,但它们很容易与具有空体的消息混淆。我们也可以使用Bool
但我们需要决定是否要使用True
或False
作为省略值。最后,我们可以使用null元组;因为它只有一个可能的值,所以它对我们来说是理想的。
实际上还有一种可能性:我们可以创建一个type alias MessageWithoutBody = { code: String }
。在某些情况下(特别是当您需要省略更多字段时),它更干净,但由于您需要复制要保留的所有字段,因此可能更加冗长。