当我们在 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 value
和 empty value
之间的区别。
Message a
是具有 1 个参数的参数化类型。您可以将其作为模板阅读,例如Message
定义中出现小写字母 a
的任何地方,它都将被替换为具体类型(String、Int 等)。
所以如果我们希望它采用 Message
和 String
主体,这就是函数的样子:
readMessage : Message String -> String
readMessage message =
这里发生的是body
字段的类型不再是a
而是String
(a
被String
代替) :
{ code : String
, body : String
}
Elm 中无值(又名 void
或 unit
)被编码为 ()
。这就是为什么具有空主体值的 Message
看起来像这样的原因:
{ code : String
, body : ()
}
但是当我们根本不关心 body 值时,我们可以只取 Message
和 any
值:
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
但随后我们需要决定是否要对省略的值使用 True
或 False
。最后,我们可以使用空元组;因为它只有一个可能的值,所以对我们来说是理想的。
实际上还有一种可能性:我们可以创建一个 type alias MessageWithoutBody = { code: String }
。这在某些情况下更清晰(尤其是当您需要省略更多字段时),但可能会更冗长,因为您需要复制所有要保留的字段。
假设我们将类型别名定义为 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 value
和 empty value
之间的区别。
Message a
是具有 1 个参数的参数化类型。您可以将其作为模板阅读,例如Message
定义中出现小写字母 a
的任何地方,它都将被替换为具体类型(String、Int 等)。
所以如果我们希望它采用 Message
和 String
主体,这就是函数的样子:
readMessage : Message String -> String
readMessage message =
这里发生的是body
字段的类型不再是a
而是String
(a
被String
代替) :
{ code : String
, body : String
}
Elm 中无值(又名 void
或 unit
)被编码为 ()
。这就是为什么具有空主体值的 Message
看起来像这样的原因:
{ code : String
, body : ()
}
但是当我们根本不关心 body 值时,我们可以只取 Message
和 any
值:
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
但随后我们需要决定是否要对省略的值使用 True
或 False
。最后,我们可以使用空元组;因为它只有一个可能的值,所以对我们来说是理想的。
实际上还有一种可能性:我们可以创建一个 type alias MessageWithoutBody = { code: String }
。这在某些情况下更清晰(尤其是当您需要省略更多字段时),但可能会更冗长,因为您需要复制所有要保留的字段。