Skip to content

Commit 4d36216

Browse files
benluojulienrf
authored andcommitted
add code tab in _zh-cn/overviews/scala3-book/fp-functional-error-handling.md
1 parent 82bc07c commit 4d36216

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed

_zh-cn/overviews/scala3-book/fp-functional-error-handling.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,28 @@ Scala 的解决方案是使用类似 `Option`/`Some`/`None` 类的结构。
3232
想象一下,您想编写一个方法,可以轻松地将字符串转换为整数值,并且您想要一种优雅的方法来处理异常,这个是异常是该方法获取类似“Hello”而不是“1”的字符串时引发的。
3333
对这种方法的初步猜测可能如下所示:
3434

35+
{% tabs fp-java-try class=tabs-scala-version %}
36+
{% tab 'Scala 2' %}
37+
```scala
38+
def makeInt(s: String): Int =
39+
try {
40+
Integer.parseInt(s.trim)
41+
} catch {
42+
case e: Exception => 0
43+
}
44+
```
45+
{% endtab %}
46+
47+
{% tab 'Scala 3' %}
3548
```scala
3649
def makeInt(s: String): Int =
3750
try
3851
Integer.parseInt(s.trim)
3952
catch
4053
case e: Exception => 0
4154
```
55+
{% endtab %}
56+
{% endtabs %}
4257

4358
如果转换成功,则此方法返回正确的 `Int` 值,但如果失败,则该方法返回 `0`
4459
出于某些目的,这可能是可以的,但它并不准确。
@@ -57,23 +72,42 @@ Scala 中这个问题的一个常见解决方案是使用三个类,称为 `Opt
5772

5873
这是 `makeInt` 的修订版:
5974

75+
{% tabs fp--try-option class=tabs-scala-version %}
76+
{% tab 'Scala 2' %}
77+
```scala
78+
def makeInt(s: String): Option[Int] =
79+
try {
80+
Some(Integer.parseInt(s.trim))
81+
} catch {
82+
case e: Exception => None
83+
}
84+
```
85+
{% endtab %}
86+
87+
{% tab 'Scala 3' %}
6088
```scala
6189
def makeInt(s: String): Option[Int] =
6290
try
6391
Some(Integer.parseInt(s.trim))
6492
catch
6593
case e: Exception => None
6694
```
95+
{% endtab %}
96+
{% endtabs %}
6797

6898
这段代码可以理解为,“当给定的字符串转换为整数时,返回包裹在 `Some` 中的 `Int`,例如 `Some(1)`
6999
当字符串无法转换为整数时,会抛出并捕获异常,并且该方法返回一个 `None` 值。”
70100

71101
这些示例展示了 `makeInt` 的工作原理:
72102

103+
{% tabs fp-try-option-example %}
104+
{% tab 'Scala 2 and 3' %}
73105
```scala
74106
val a = makeInt("1") // Some(1)
75107
val b = makeInt("one") // None
76108
```
109+
{% endtab %}
110+
{% endtabs %}
77111

78112
如图所示,字符串`"1"`产生一个 `Some(1)`,而字符串 `"one"` 产生一个 `None`
79113
这是错误处理的 `Option` 方法的本质。
@@ -99,11 +133,24 @@ val b = makeInt("one") // None
99133

100134
一种可能的解决方案是使用 `match` 表达式:
101135

136+
{% tabs fp-option-match class=tabs-scala-version %}
137+
{% tab 'Scala 2' %}
138+
```scala
139+
makeInt(x) match {
140+
case Some(i) => println(i)
141+
case None => println("That didn’t work.")
142+
}
143+
```
144+
{% endtab %}
145+
146+
{% tab 'Scala 3' %}
102147
```scala
103148
makeInt(x) match
104149
case Some(i) => println(i)
105150
case None => println("That didn’t work.")
106151
```
152+
{% endtab %}
153+
{% endtabs %}
107154

108155
在本例中,如果 `x` 可以转换为 `Int`,则计算第一个 `case` 子句右侧的表达式;如果 `x` 不能转换为 `Int`,则计算第二个 `case` 子句右侧的表达式。
109156

@@ -113,6 +160,20 @@ makeInt(x) match
113160
例如,假设您要将三个字符串转换为整数值,然后将它们相加。
114161
这就是你使用 `for` 表达式和 `makeInt` 的方法:
115162

163+
{% tabs fp-for-comprehension class=tabs-scala-version %}
164+
{% tab 'Scala 2' %}
165+
```scala
166+
val y = for {
167+
a <- makeInt(stringA)
168+
b <- makeInt(stringB)
169+
c <- makeInt(stringC)
170+
} yield {
171+
a + b + c
172+
}
173+
```
174+
{% endtab %}
175+
176+
{% tab 'Scala 3' %}
116177
```scala
117178
val y = for
118179
a <- makeInt(stringA)
@@ -121,6 +182,8 @@ val y = for
121182
yield
122183
a + b + c
123184
```
185+
{% endtab %}
186+
{% endtabs %}
124187

125188
在该表达式运行后,`y` 将是以下两种情况之一:
126189

@@ -129,6 +192,24 @@ yield
129192

130193
你可以自己测试一下:
131194

195+
{% tabs fp-for-comprehension-evaluation class=tabs-scala-version %}
196+
{% tab 'Scala 2' %}
197+
```scala
198+
val stringA = "1"
199+
val stringB = "2"
200+
val stringC = "3"
201+
202+
val y = for {
203+
a <- makeInt(stringA)
204+
b <- makeInt(stringB)
205+
c <- makeInt(stringC)
206+
} yield {
207+
a + b + c
208+
}
209+
```
210+
{% endtab %}
211+
212+
{% tab 'Scala 3' %}
132213
```scala
133214
val stringA = "1"
134215
val stringB = "2"
@@ -141,15 +222,21 @@ val y = for
141222
yield
142223
a + b + c
143224
```
225+
{% endtab %}
226+
{% endtabs %}
144227

145228
使用该样本数据,变量 `y` 的值将是 `Some(6)`
146229

147230
要查看失败案例,请将这些字符串中的任何一个更改为不会转换为整数的字符串。
148231
当你这样做时,你会看到 `y``None`
149232

233+
{% tabs fp-for-comprehension-failure-result %}
234+
{% tab 'Scala 2 and 3' %}
150235
```scala
151236
y: Option[Int] = None
152237
```
238+
{% endtab %}
239+
{% endtabs %}
153240

154241
## 将 Option 视为容器
155242

@@ -171,10 +258,14 @@ They have many of the methods you’d expect from a collection class, including
171258

172259
This raises an interesting question: What will these two values print, if anything?
173260

261+
{% tabs fp-option-methods-evaluation %}
262+
{% tab 'Scala 2 and 3' %}
174263
```scala
175264
makeInt("1").foreach(println)
176265
makeInt("x").foreach(println)
177266
```
267+
{% endtab %}
268+
{% endtabs %}
178269

179270
Answer: The first example prints the number `1`, and the second example doesn’t print anything.
180271
The first example prints `1` because:
@@ -196,12 +287,30 @@ Somewhere in Scala’s history, someone noted that the first example (the `Some`
196287
*But* despite having two different possible outcomes, the great thing with `Option` is that there’s really just one path: The code you write to handle the `Some` and `None` possibilities is the same in both cases.
197288
The `foreach` examples look like this:
198289

290+
{% tabs fp-another-option-method-example %}
291+
{% tab 'Scala 2 and 3' %}
199292
```scala
200293
makeInt(aString).foreach(println)
201294
```
295+
{% endtab %}
296+
{% endtabs %}
202297

203298
And the `for` expression looks like this:
204299

300+
{% tabs fp-another-for-comprehension-example class=tabs-scala-version %}
301+
{% tab 'Scala 2' %}
302+
```scala
303+
val y = for {
304+
a <- makeInt(stringA)
305+
b <- makeInt(stringB)
306+
c <- makeInt(stringC)
307+
} yield {
308+
a + b + c
309+
}
310+
```
311+
{% endtab %}
312+
313+
{% tab 'Scala 3' %}
205314
```scala
206315
val y = for
207316
a <- makeInt(stringA)
@@ -210,16 +319,31 @@ val y = for
210319
yield
211320
a + b + c
212321
```
322+
{% endtab %}
323+
{% endtabs %}
213324

214325
With exceptions you have to worry about handling branching logic, but because `makeInt` returns a value, you only have to write one piece of code to handle both the Happy and Unhappy Paths, and that simplifies your code.
215326

216327
Indeed, the only time you have to think about whether the `Option` is a `Some` or a `None` is when you handle the result value, such as in a `match` expression:
217328

329+
{% tabs fp-option-match-handle class=tabs-scala-version %}
330+
{% tab 'Scala 2' %}
331+
```scala
332+
makeInt(x) match {
333+
case Some(i) => println(i)
334+
case None => println("That didn't work.")
335+
}
336+
```
337+
{% endtab %}
338+
339+
{% tab 'Scala 3' %}
218340
```scala
219341
makeInt(x) match
220342
case Some(i) => println(i)
221343
case None => println("That didn't work.")
222344
```
345+
{% endtab %}
346+
{% endtabs %}
223347

224348
> There are several other ways to handle `Option` values.
225349
> See the reference documentation for more details.
@@ -229,6 +353,8 @@ makeInt(x) match
229353

230354
回到 `null` 值,`null` 值可以悄悄地潜入你的代码的地方是这样的类:
231355

356+
{% tabs fp=case-class-nulls %}
357+
{% tab 'Scala 2 and 3' %}
232358
```scala
233359
class Address(
234360
var street1: String,
@@ -238,10 +364,26 @@ class Address(
238364
var zip: String
239365
)
240366
```
367+
{% endtab %}
368+
{% endtabs %}
241369

242370
虽然地球上的每个地址都有一个 `street1` 值,但 `street2` 值是可选的。
243371
因此,`street2` 字段可以被分配一个 `null` 值:
244372

373+
{% tabs fp-case-class-nulls-example class=tabs-scala-version %}
374+
{% tab 'Scala 2' %}
375+
```scala
376+
val santa = new Address(
377+
"1 Main Street",
378+
null, // <-- D’oh! A null value!
379+
"North Pole",
380+
"Alaska",
381+
"99705"
382+
)
383+
```
384+
{% endtab %}
385+
386+
{% tab 'Scala 3' %}
245387
```scala
246388
val santa = Address(
247389
"1 Main Street",
@@ -251,10 +393,14 @@ val santa = Address(
251393
"99705"
252394
)
253395
```
396+
{% endtab %}
397+
{% endtabs %}
254398

255399
从历史上看,开发人员在这种情况下使用了空白字符串和空值,这两种方法都是使用技巧来解决基础性的问题,这个问题是:`street2` 是一个*可选*字段。
256400
在 Scala 和其他现代语言中,正确的解决方案是预先声明 `street2` 是可选的:
257401

402+
{% tabs fp-case-class-with-options %}
403+
{% tab 'Scala 2 and 3' %}
258404
```scala
259405
class Address(
260406
var street1: String,
@@ -264,9 +410,25 @@ class Address(
264410
var zip: String
265411
)
266412
```
413+
{% endtab %}
414+
{% endtabs %}
267415

268416
现在开发人员可以编写更准确的代码,如下所示:
269417

418+
{% tabs fp-case-class-with-options-example-none class=tabs-scala-version %}
419+
{% tab 'Scala 2' %}
420+
```scala
421+
val santa = new Address(
422+
"1 Main Street",
423+
None, // 'street2' has no value
424+
"North Pole",
425+
"Alaska",
426+
"99705"
427+
)
428+
```
429+
{% endtab %}
430+
431+
{% tab 'Scala 3' %}
270432
```scala
271433
val santa = Address(
272434
"1 Main Street",
@@ -276,9 +438,25 @@ val santa = Address(
276438
"99705"
277439
)
278440
```
441+
{% endtab %}
442+
{% endtabs %}
279443

280444
或这个:
281445

446+
{% tabs fp-case-class-with-options-example-some class=tabs-scala-version %}
447+
{% tab 'Scala 2' %}
448+
```scala
449+
val santa = new Address(
450+
"123 Main Street",
451+
Some("Apt. 2B"),
452+
"Talkeetna",
453+
"Alaska",
454+
"99676"
455+
)
456+
```
457+
{% endtab %}
458+
459+
{% tab 'Scala 3' %}
282460
```scala
283461
val santa = Address(
284462
"123 Main Street",
@@ -288,6 +466,8 @@ val santa = Address(
288466
"99676"
289467
)
290468
```
469+
{% endtab %}
470+
{% endtabs %}
291471

292472
## `Option` 不是唯一的解决方案
293473

0 commit comments

Comments
 (0)