为什么 Maybe 包括 Just?
Why does Maybe include Just?
多亏了some excellent answers here,我大体上理解了(显然是有限的)Haskell的Maybe
的目的,它的定义是
data Maybe a = Nothing | Just a
但是我不是很清楚为什么 Just
是这个定义的一部分。据我所知,这是定义 Just
本身的地方,但相关文档并没有说明太多。
我认为在 Maybe
的定义中使用 Just
的主要好处是否正确,而不是简单地使用
data Maybe a = Nothing | a
是否允许与 Just _
进行模式匹配以及 isJust
和 fromJust
等有用的功能?
为什么Maybe
定义的是前者而不是后者?
Haskell 的代数数据类型是 标记联合 。按照设计,当您将两种不同的类型组合成另一种类型时,它们 具有 构造函数来消除它们的歧义。
您的定义不符合代数数据类型的工作方式。
data Maybe a = Nothing | a
此处 a
没有 "tag"。在您的情况下,我们如何区分 Maybe a
和正常的未包装 a
?
Maybe
有一个 Just
构造函数,因为它 有 有一个构造函数 设计 .
其他语言确实有 union types,可以像您想象的那样工作,但它们不适合 Haskell。它们在实践中的表现不同,并且往往有点容易出错。
与普通联合类型相比,更喜欢标记联合有一些强有力的设计原因。它们在类型推断方面表现出色。无论如何,真实代码中的联合通常都有一个标签。而且,从优雅的角度来看,标记联合 自然适合 语言,因为它们是 products 的对偶(即元组和记录)。如果你很好奇,我在博客中写过这个 post introducing and motivating algebraic data types.
脚注
¹ 我在两个地方玩过联合类型:TypeScript 和 C。TypeScript 编译为动态类型的 JavaScript,这意味着它在运行时跟踪值的类型——基本上是一个标签.
C 没有,但在实践中,大约 90% 的联合类型使用都有标签或有效地模拟结构子类型。我的一位教授实际上对如何在实际 C 代码中使用联合进行了实证研究,但我不记得它是在哪篇论文中发表的。
Just
是一个构造函数,a
本身就是 a
类型,当 Just a
构造一个不同的类型时 Maybe a
.
另一种看待它的方式(除了 Tikhon 的回答)是考虑另一种基本 Haskell 类型,Either
,它的定义如下:
-- | A value that contains either an @a@ (the 'Left') constructor) or
-- a @b@ (the 'Right' constructor).
data Either a b = Left a | Right b
这允许您拥有如下值:
example1, example2 :: Either String Int
example1 = Left "Hello!"
example2 = Right 42
...但也喜欢这个:
example3, example4 :: Either String String
example3 = Left "Hello!"
example4 = Right "Hello!"
类型 Either String String
,您第一次遇到它时,听起来像 "either a String
or a String
,",因此您可能认为它与 String
相同。但事实并非如此,因为 Haskell 联合是 标记的 联合,因此 Either String String
不仅记录了 String
,还记录了"tags"(数据构造函数;在本例中为 Left
和 Right
)用于构造它。因此,即使这两种选择都将 String
作为其有效负载,您也可以分辨出任何一个值最初是如何构建的。这很好,因为在很多情况下,替代项的类型相同,但 constructors/tags 赋予额外的含义:
data ResultMessage = FailureMessage String | SuccessMessage String
这里的数据构造函数是FailureMessage
和SuccessMessage
,你可以从名字中猜到,即使这两种情况下的payload都是String
,它们的意思也大不相同东西!
所以把它带回到 Maybe
/Just
,这里发生的是 Haskell 只是统一地工作:联合类型的每个备选方案都有一个不同的数据构造函数,它必须始终用于构造和模式匹配其类型的值。即使一开始您可能认为可以从上下文中猜出它,但它并没有做到。
还有其他原因,技术性更强一些。首先,懒惰评估的规则是根据数据构造函数定义的。简短版本:惰性计算意味着如果 Haskell 被迫查看类型 Maybe a
的值内部,它将尝试做最少的工作来弄清楚它是否看起来像 Nothing
或类似 Just x
— 最好不要在执行此操作时查看 x
内部。
其次:语言需要能够区分 Maybe a
、Maybe (Maybe a)
和 Maybe (Maybe (Maybe a))
等类型。如果你考虑一下,如果我们有一个像你写的那样工作的类型定义:
data Maybe a = Nothing | a -- NOT VALID HASKELL
...我们想创建一个 Maybe (Maybe a)
类型的值,您将无法区分这两个值:
example5, example6 :: Maybe (Maybe a)
example5 = Nothing
example6 = Just Nothing
乍一看这似乎有点傻,但想象一下您有一个值为 "nullable":
的地图
-- Map of persons to their favorite number. If we know that some person
-- doesn't have a favorite number, we store `Nothing` as the value for
-- that person.
favoriteNumber :: Map Person (Maybe Int)
...并想查找条目:
Map.lookup :: Ord k => Map k v -> k -> Maybe v
因此,如果我们在地图中查找 mary
,我们有:
Map.lookup favoriteNumber mary :: Maybe (Maybe Int)
现在结果 Nothing
表示玛丽不在地图中,而 Just Nothing
表示玛丽在地图中但她没有最喜欢的号码。
Maybe a
被设计为比 a
类型多一个值。在类型论中,有时它被写成 1 + a
(直到 iso),这使得这个事实更加明显。
作为实验,考虑类型 Maybe (Maybe Bool)
。这里我们有 1 + 1 + 2
个值,即:
Nothing
Just Nothing
Just (Just False)
Just (Just True)
如果允许我们定义
data Maybe a = Nothing | a
我们将失去区分 Just Nothing
和 Nothing
的情况,因为不再有 Just
来区分它们。事实上,Maybe (Maybe a)
会崩溃为 Maybe a
。这将是一个不方便的特殊情况。
多亏了some excellent answers here,我大体上理解了(显然是有限的)Haskell的Maybe
的目的,它的定义是
data Maybe a = Nothing | Just a
但是我不是很清楚为什么 Just
是这个定义的一部分。据我所知,这是定义 Just
本身的地方,但相关文档并没有说明太多。
我认为在 Maybe
的定义中使用 Just
的主要好处是否正确,而不是简单地使用
data Maybe a = Nothing | a
是否允许与 Just _
进行模式匹配以及 isJust
和 fromJust
等有用的功能?
为什么Maybe
定义的是前者而不是后者?
Haskell 的代数数据类型是 标记联合 。按照设计,当您将两种不同的类型组合成另一种类型时,它们 具有 构造函数来消除它们的歧义。
您的定义不符合代数数据类型的工作方式。
data Maybe a = Nothing | a
此处 a
没有 "tag"。在您的情况下,我们如何区分 Maybe a
和正常的未包装 a
?
Maybe
有一个 Just
构造函数,因为它 有 有一个构造函数 设计 .
其他语言确实有 union types,可以像您想象的那样工作,但它们不适合 Haskell。它们在实践中的表现不同,并且往往有点容易出错。
与普通联合类型相比,更喜欢标记联合有一些强有力的设计原因。它们在类型推断方面表现出色。无论如何,真实代码中的联合通常都有一个标签。而且,从优雅的角度来看,标记联合 自然适合 语言,因为它们是 products 的对偶(即元组和记录)。如果你很好奇,我在博客中写过这个 post introducing and motivating algebraic data types.
脚注
¹ 我在两个地方玩过联合类型:TypeScript 和 C。TypeScript 编译为动态类型的 JavaScript,这意味着它在运行时跟踪值的类型——基本上是一个标签.
C 没有,但在实践中,大约 90% 的联合类型使用都有标签或有效地模拟结构子类型。我的一位教授实际上对如何在实际 C 代码中使用联合进行了实证研究,但我不记得它是在哪篇论文中发表的。
Just
是一个构造函数,a
本身就是 a
类型,当 Just a
构造一个不同的类型时 Maybe a
.
另一种看待它的方式(除了 Tikhon 的回答)是考虑另一种基本 Haskell 类型,Either
,它的定义如下:
-- | A value that contains either an @a@ (the 'Left') constructor) or
-- a @b@ (the 'Right' constructor).
data Either a b = Left a | Right b
这允许您拥有如下值:
example1, example2 :: Either String Int
example1 = Left "Hello!"
example2 = Right 42
...但也喜欢这个:
example3, example4 :: Either String String
example3 = Left "Hello!"
example4 = Right "Hello!"
类型 Either String String
,您第一次遇到它时,听起来像 "either a String
or a String
,",因此您可能认为它与 String
相同。但事实并非如此,因为 Haskell 联合是 标记的 联合,因此 Either String String
不仅记录了 String
,还记录了"tags"(数据构造函数;在本例中为 Left
和 Right
)用于构造它。因此,即使这两种选择都将 String
作为其有效负载,您也可以分辨出任何一个值最初是如何构建的。这很好,因为在很多情况下,替代项的类型相同,但 constructors/tags 赋予额外的含义:
data ResultMessage = FailureMessage String | SuccessMessage String
这里的数据构造函数是FailureMessage
和SuccessMessage
,你可以从名字中猜到,即使这两种情况下的payload都是String
,它们的意思也大不相同东西!
所以把它带回到 Maybe
/Just
,这里发生的是 Haskell 只是统一地工作:联合类型的每个备选方案都有一个不同的数据构造函数,它必须始终用于构造和模式匹配其类型的值。即使一开始您可能认为可以从上下文中猜出它,但它并没有做到。
还有其他原因,技术性更强一些。首先,懒惰评估的规则是根据数据构造函数定义的。简短版本:惰性计算意味着如果 Haskell 被迫查看类型 Maybe a
的值内部,它将尝试做最少的工作来弄清楚它是否看起来像 Nothing
或类似 Just x
— 最好不要在执行此操作时查看 x
内部。
其次:语言需要能够区分 Maybe a
、Maybe (Maybe a)
和 Maybe (Maybe (Maybe a))
等类型。如果你考虑一下,如果我们有一个像你写的那样工作的类型定义:
data Maybe a = Nothing | a -- NOT VALID HASKELL
...我们想创建一个 Maybe (Maybe a)
类型的值,您将无法区分这两个值:
example5, example6 :: Maybe (Maybe a)
example5 = Nothing
example6 = Just Nothing
乍一看这似乎有点傻,但想象一下您有一个值为 "nullable":
的地图-- Map of persons to their favorite number. If we know that some person
-- doesn't have a favorite number, we store `Nothing` as the value for
-- that person.
favoriteNumber :: Map Person (Maybe Int)
...并想查找条目:
Map.lookup :: Ord k => Map k v -> k -> Maybe v
因此,如果我们在地图中查找 mary
,我们有:
Map.lookup favoriteNumber mary :: Maybe (Maybe Int)
现在结果 Nothing
表示玛丽不在地图中,而 Just Nothing
表示玛丽在地图中但她没有最喜欢的号码。
Maybe a
被设计为比 a
类型多一个值。在类型论中,有时它被写成 1 + a
(直到 iso),这使得这个事实更加明显。
作为实验,考虑类型 Maybe (Maybe Bool)
。这里我们有 1 + 1 + 2
个值,即:
Nothing
Just Nothing
Just (Just False)
Just (Just True)
如果允许我们定义
data Maybe a = Nothing | a
我们将失去区分 Just Nothing
和 Nothing
的情况,因为不再有 Just
来区分它们。事实上,Maybe (Maybe a)
会崩溃为 Maybe a
。这将是一个不方便的特殊情况。