当我们在 Elm 中使用类型变量定义类型别名时,底层会发生什么

What happens under the hood when we define a type alias with a type variable in Elm

假设我们将类型别名定义为 Message 为:

type alias Message a =
{ code : String
, body : a
}

然后定义函数readMessage为:

readMessage : Message () -> String
readMessage message =
...

以上示例来自 Elm 教程,书上说:

This function takes Message with an empty body. This is not the same as any value, just an empty one.

谁能详细说明上面的场景到底发生了什么,以及编译器是如何处理的。

除非你真的想看到它的内部编译器表示,否则我认为这里重要的是 any valueempty value 之间的区别。

Message a 是具有 1 个参数的参数化类型。您可以将其作为模板阅读,例如Message 定义中出现小写字母 a 的任何地方,它都将被替换为具体类型(String、Int 等)。

所以如果我们希望它采用 MessageString 主体,这就是函数的样子:

readMessage : Message String -> String
readMessage message =

这里发生的是body字段的类型不再是a而是StringaString代替) :

{ code : String
, body : String
}

Elm 中无值(又名 voidunit)被编码为 ()。这就是为什么具有空主体值的 Message 看起来像这样的原因:

{ code : String
, body : ()
}

但是当我们根本不关心 body 值时,我们可以只取 Messageany 值:

readMessage : Message a -> String
readMessage message =

小写的a可以是任何小写的字符串,我们可以这样让它更具可读性:

readMessage : Message any -> String
readMessage message =

但是我们无法真正读取消息正文,因为我们不知道它的类型(所以我们不知道如何读取它)。

希望对您有所帮助。

类型 Message () 是以下记录的别名:

{ code : String
, body : ()
}

其中 () 类型表示没有任何项的元组(也称为 空元组 )。这种类型的值只有一个,也写成().

现在,当我们想要省略记录中的某些字段时,我们不能不指定它——这会让编译器理所当然地生气,另请参见 The Billion Dollar Mistake。我们需要告诉编译器这个值可以省略。

我们可以做到的一种方法是使用 Maybe 类型,但是如果我们制作一个消息列表,这将允许我们在某些消息中包含正文并在其他消息中省略它。这可能不是我们想要的。

另一种方法是像您在问题中所做的那样参数化 Message 类型。这将允许我们在阅读消息时使用带有 String 正文的消息,而当我们对正文不感兴趣时​​使用不同的正文类型。

在这种情况下,我们需要考虑体型应该是什么。虽然我们可以使用空 String 来表示消息体被省略的消息,但它们很容易与消息体为空的消息混淆。我们也可以使用 Bool 但随后我们需要决定是否要对省略的值使用 TrueFalse。最后,我们可以使用空元组;因为它只有一个可能的值,所以对我们来说是理想的。

实际上还有一种可能性:我们可以创建一个 type alias MessageWithoutBody = { code: String }。这在某些情况下更清晰(尤其是当您需要省略更多字段时),但可能会更冗长,因为您需要复制所有要保留的字段。