谈谈 Nim 语言的隐式返回值

2024-06-16 乙酉 甲辰年 庚午 辛亥- 农历五月十一 - 多云,晚上雨

海云青飞 翻译

Nim 语言的 Proc 有三种返回值的方式

新兴起的 Nim 编程语言与其他一些语言(尤其是函数式编程语言)共享的一个特性是隐式返回值。这个小特性看似只是避免到处写return,但实际上它的用途远不止于此!那么,什么是隐式返回呢?在 Nim 中,我们有三种从过程中返回值的方式:

  • 你可以用 return 进行显式返回
  • 可以通过 result 变量进行隐式返回
  • 还可以通过过程中的最后一个语句进行隐式返回

考虑以下简单的代码:

proc explicit(): string =
  return "Typical explicit return"

proc implicitResult(): string =
  result = "Implicit return through result"

proc implicitLastStatement(): string =
  "Implicit return since it's last statement"
  • 第一个过程使用了你可能熟悉的典型return

  • 第二个过程使用了在每个有返回值的过程中都可用的result变量,这在 Nim 中被广泛用于构建返回值或在过程结束前分配返回值

  • 第三种方式可能是三者中最有趣的,也是本文主要讨论的内容

    因为 Nim 中的大多数块都会隐式返回它们最后一个语句的结果,只要它与返回类型匹配。如果你改变上述语句的返回类型,它会告诉你需要丢弃字符串

    在这个例子中,字符串是一个字面量,但它也可能是对另一个返回字符串的过程的调用。正如你可能知道的,在 Nim 中未使用的返回变量需要被丢弃,但如果它是过程中的最后一个语句,它将被返回

Nim 语言隐式返回值,与 C 语言比较

正如我上面提到的,这可能看起来像是一个微不足道的特性,只是节省了我们在过程中输入 returnresult = 的时间。但它不仅仅是这样!因为不仅是过程具有这种能力,大多数代码块也是如此。以C语言中的经典三元运算符为例:

int x = somecall() ? 100 : 200;

这将在 somecall 返回被评估为真的东西时返回 100,否则返回 200。这是一种基于某些条件在单行中分配变量的好方法。但 Nim 没有三元运算符,你在 Nim 中要做的是简单地使用常规的 if 语句:

let x = if somecall(): 100 else: 200

这与上面做的事情完全相同,它之所以有效,是因为 if 语句会隐式返回最后一个语句的结果,就像 proc 一样。海云青飞 个人认为,Nim if 语句的隐式返回值比 C 语言的三元运算符更加直观,也就是,C 语言版,如果你没有学过 C 语言的语法就未必知道其含义,而 Nim 版则只要认识用到的英语单词就能直观知道其意思

当然,这不仅适用于简单的 if/else 情况,也适用于 elif

let x = if somecall(): 100
  elif othercall(): 150
  else: 200

正如你在这里看到的,你当然也可以像常规 if 语句一样将这些分成多行。这样的 if 语句必须保证返回某些东西才能工作。所以如果你去掉 else ,或者如果其中一个分支没有返回某些东西,你将得到一个关于必须丢弃其他值的错误

Nim 的隐式返回值让代码更简洁、安全

这里就可以引出第一个酷炫的 Nim 语言隐式返回值的用法。想象一下,你有一个需要根据一些复杂条件分配的值:

var y = 300
if somecall() > 100:
  dosomething(@["item1", "item2"])
  y = 100
elif someothercall() < thirdcall():
  anotheraction("Hello world")
  y = 200

这可能是你在另一种语言中使用的模式。但可以想象可能出现的意外情况,比如对此进行重构以添加另一个分支,并忘记设置y,或者不小心删除了现有的一个。但是,有了隐式返回,我们可以让编译器检查我们实际上是否设置了 y

let y =
  if somecall() > 100:
    dosomething(@["item1", "item2"])
    100
  elif someothercall() < thirdcall():
    anotheraction("Hello world")
    200
  else:
    300

这确保了 y 得到了一个值,由于这些只是完全正常的 if 语句,你当然可以在其中放入任何逻辑,并让块中的最后一个语句成为返回值。当然,包括另一个 if

但乐趣还没有结束。你可能已经注意到,第二个例子使用了 let 赋值而不是 var。没错,使用这种初始化方法意味着赋值可以是不可变的!以防你稍后不小心做了一些愚蠢的事情,这是一个额外的安全措施

Nim 语言 case 语句使用隐式返回值

正如我上面提到的,这适用于所有类型的块。所以你可以使用case语句:

let z = case somecall():
  of int.low..1000:
    "Low"
  of 1001..2000:
    "Medium"
  else:
    "High"

Nim 语言 try 语句使用隐式返回值

或者如何在异常情况下使用try语句设置默认值?

import strutils
let a = try: parseFloat("100.0") except ValueError: 0.0

不再需要编写四行代码来进行适当的错误处理而只是为了使用默认值

Nim 语言的隐式返回值解决计算科学二大难题之一:命名

另一个酷炫的事情是解决计算机科学中的两个难题之一

计算机科学中只有两件难事:缓存失效和命名事物 – Phil Karlton

由于 Nim 还有一个简单的 block 语句,我们可以创建任意代码的块。由于一切都有隐式返回,我们可以使用它们在分配变量时创建我们自己的迷你作用域:

let myAccumulation =
  block:
    var x = ""
    for i in 1..5:
      x &= $i & " "
    x

这里的变量 x 在该块之外是不可见的,这意味着我们不能意外地使用它而不是 myAccumulation。由于它只存在于这个范围内,我们可以重用这个名字,所以我们可以很容易地创建小的临时变量,这些变量只存在于另一个值的赋值范围内,然后很快就会被遗忘,当然,然后就将它们称为 tmp

海云青飞:创作是为了更快理解事物,文章要有次级标题

我在数年前就阅读过此文,说实话,我对读过的多数文章不大满意,因为它们没有遵循我规定的创作原则。创作是为了让别人和自己更快地理解事物,于是就有一些讲究,详见 概念化+醒目+形象化:对人类友好的文章内容设计

今天我翻译此文时,就顺便加了些次级标题,我自己对文章的理解就立即得到了很大的改观。我翻译文章并不是死板地逐字逐句翻译,而是改进一切可以改进的地方

相关内容

  • 原文《Tips and tricks with implicit return in Nim》 - 1st November 2019 - https://peterme.net/tips-and-tricks-with-implicit-return-in-nim.html

独立思考最难得,赞赏支持是美德!(微信扫描下图)