Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
CziSKY committed May 13, 2024
1 parent ae64e1f commit 57cd9ac
Showing 1 changed file with 5 additions and 5 deletions.
10 changes: 5 additions & 5 deletions content/alexis_king_parse_don-t-validate/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ warning: [-Wincomplete-patterns]
In an equation for ‘head’: Patterns not matched: []
```

错误信息很有帮助地指出了我们的函数是部分函数(Partial Function),也就是说它并不适用于所有可能的输入。具体来说,这里没有定义当输入为 `[]` 的情况,即空列表。所以这里的报错是有道理的,因为列表为空我们就不可能返回列表的第一个元素 —— 没有元素可以返回!因此这个函数在定义上不是全函数(Total Function)。
错误信息很有帮助地指出了我们的函数是完全函数(Partial Function),也就是说它并不适用于所有可能的输入。具体来说,这里没有定义当输入为 `[]` 的情况,即空列表。所以这里的报错是有道理的,因为列表为空我们就不可能返回列表的第一个元素 —— 没有元素可以返回!因此这个函数在定义上不是全函数(Total Function)。

## 将部分函数重构成全函数
## 将完全函数重构成全函数

对于只具有动态类型语言背景的人来说,这可能看起来有些复杂。如果我们有一个列表,我们可能确实想要获取它的第一个元素。「获取列表的第一个元素」的操作在 Haskell 是可以实现的,它只是需要一些额外的仪式。有两种不同的方法可以修正 `head` 函数,我们将从最简单的方法开始。

## 调整期望值

如前所属,`head` 是部分函数。因为如果列表为空时没有元素可以返回:我们做出了一个我们根本无法实现的承诺。幸运的是,有一个简单的解决方案可以解决这个困境:我们可以弱化我们的承诺。由于我们不能保证调用者能够从列表中得到一个元素,我们将尽可能返回一个元素,但我们保留完全不返回任何东西的权利。在 Haskell 中,我们用 `Maybe` 类型来表达这种可能性:
如前所属,`head` 是完全函数。因为如果列表为空时没有元素可以返回:我们做出了一个我们根本无法实现的承诺。幸运的是,有一个简单的解决方案可以解决这个困境:我们可以弱化我们的承诺。由于我们不能保证调用者能够从列表中得到一个元素,我们将尽可能返回一个元素,但我们保留完全不返回任何东西的权利。在 Haskell 中,我们用 `Maybe` 类型来表达这种可能性:

```haskell
head :: [a] -> Maybe a
Expand Down Expand Up @@ -101,7 +101,7 @@ main = do

在前面的部分中,我们尝试修改 `head` 函数的实现,但仍有不足之处。我们希望这个函数能更加智能:如果我们已经验证过列表非空,`head` 应该能直接返回列表的第一个元素,而不需要我们处理我们已知不可能发生的情况。那么,我们该如何做呢?

让我们再次回顾一下 `head` 函数的原始(部分函数版本)类型签名:
让我们再次回顾一下 `head` 函数的原始(完全函数版本)类型签名:

```haskell
head :: [a] -> a
Expand Down Expand Up @@ -180,7 +180,7 @@ parseNonEmpty [] = throwIO $ userError "list cannot be empty"

这两个函数几乎相同:它们检查提供的列表是否为空,如果为空,则中断程序并显示错误消息。然而,它们之间有一个重要的区别:`validateNonEmpty` 始终返回 `()`,一个不携带任何信息的类型。而 `parseNonEmpty` 返回 `NonEmpty a`,这是基于输入类型 `[a]` 的一种改进,它在类型系统中保留了列表非空的信息。这两个函数检查的是同一件事,但 `parseNonEmpty` 允许调用者查看解析结果,而 `validateNonEmpty` 则把它丢了。

思考一下:什么是解析器(Parser)?实际上,解析器仅是一种函数,它处理结构较松散的输入,并输出结构更丰富的结果。由于其本质,解析器是一个部分函数 —— 即其定义域中的某些值在值域中无对应值,因此所有解析器必须具备处理失败的能力。虽然解析器常常处理的是文本数据,但这并非必然要求,例如 `parseNonEmpty` 就是一个极好的例证:它将普通列表解析成非空列表,一旦发现列表为空,则通过抛出错误信息终止程序,从而表明失败。
思考一下:什么是解析器(Parser)?实际上,解析器仅是一种函数,它处理结构较松散的输入,并输出结构更丰富的结果。由于其本质,解析器是一个完全函数 —— 即其定义域中的某些值在值域中无对应值,因此所有解析器必须具备处理失败的能力。虽然解析器常常处理的是文本数据,但这并非必然要求,例如 `parseNonEmpty` 就是一个极好的例证:它将普通列表解析成非空列表,一旦发现列表为空,则通过抛出错误信息终止程序,从而表明失败。

在这种灵活的定义下,解析器是一个非常强大的工具:它们允许在程序与外部世界的边界上提前进行输入检查,一旦完成这些检查,就不再需要再次检查!Haskellers 很清楚这种力量,他们经常在不同地方使用解析器:

Expand Down

0 comments on commit 57cd9ac

Please sign in to comment.