头图

计算一个数的绝对值是非常基础的操作,几乎所有主流的编程语言都内置了相应的函数或方法。

PHPPythonSQL 等语言中,直接调用 abs() 函数即可,例如 abs(-1)。到了 JavaC# 这类面向对象的语言中,abs() 通常是 Math 类的静态方法,调用时要加上前缀 Math.,即 Math.abs(-1)

Go 语言就要稍微麻烦一点了,因为 math 包中的 Abs() 函数仅支持 float64 类型的参数,如果要计算整数的绝对值,就不得不先手写一个 func abs(x int)。常刷 LeetCode 的同学对此一定深有体会——在写实际算法之前,先要定义好 min()max()abs() 等辅助函数,都快形成肌肉记忆了吧。

本文的主角 Ruby 则更加特别,竟支持 -1.abs() 这样的写法。也就是说,整数(字面量)可以直接“点”一个 abs() 方法,这与大多数语言的风格都不同。

puts -1.abs()

可这也算不上 Ruby 的“奇葩”之处吧。因为在 Rust、C#、Kotlin 等语言中,类似 -1.abs() 的写法并不稀奇。这些语言都提供了某种机制,使整数可以直接调用方法。

例如,在 C# 中,可以通过 扩展方法 (extension methods)实现类似的效果:

using System;

public static class IntExtensions {
  public static int Abs(this int value) => Math.Abs(value);
}

class Program {
  static void Main() {
    Console.WriteLine(-1.Abs());
  }
}

Rust 则干脆直接支持 Ruby 的这种写法,只不过要先将 -1 通过 _f64 后缀转换为 float64 类型:

fn main() {
    println!("{}", -1_f64.abs());
}

那为什么还说 Ruby “奇葩” 呢?请思考一下,-1.abs() 的结果应该是多少?

  • 是 -1 的绝对值吗,即 (-1).abs() == 1
  • 还是 1 的绝对值的相反数,即 -(1.abs()) == -1

在 Rust、C#、Kotlin、Swift 等一众语言中,-1.abs() 的结果都是 -1。而在 Ruby 中,-1.abs() 的结果是 1。表面上看,Ruby 反倒是“奇葩”的那个,但如果从直觉出发,Ruby 的行为比 Rust、C# 这些语言更贴近人们的理解吧

从左往右读,-1.abs() 就是“负 1 的绝对值”,结果自然应该是 1。而 Rust 等语言的计算顺序却如同 “先计算 1.abs(),然后才想起来前面还一个负号呢,再取相反数”,得到 -1

那 Ruby 到底是”众人皆醉我独醒“,还是“旁人清醒独我迷”呢?

其实,“奇葩”的 Ruby 没有错,Rust、C# 阵营也没有错。这并不是对错的问题,而是不同语言(的设计者)对于 运算符优先级 的不同选择: -(负号)和 .(方法调用)谁的优先级更高?

我们都熟悉 “先乘除,后加减”的数学运算优先级,但在编程语言的规范和设计中,.- 谁先执行并没有公认的标准,语言的设计者会根据自己的理念做出选择。

Ruby 的创造者 松本行弘(Matz) 曾提到:“Ruby 的设计原则之一是让编程更快乐。

相比于 因为不了解 .- 的优先级而踩坑,排查半天,本以为发现了什么“惊天 Bug”而沾沾自喜,最后却一盆凉水浇下来,发现只是少加了个括号——还不如 Ruby 这种更符合直觉的“奇葩”行为更让人快乐。

毕竟,直觉和可读性也是编程体验的重要部分。Ruby 选择的方式可能小众,但却更符合人们的思维习惯。


如果习惯了 Rust、C# 阵营的解析方式,可能会觉得 Ruby “奇葩”;但对于 Ruby 的开发者而言,可能又会觉得 -1.abs() == -1 才不合理。

没有绝对的对错,只有不同的选择。


da_miao_zi
1 声望0 粉丝

软件工程师、技术图书译者。译有《图解云计算架构》《图解量子计算机》《计算机是怎样跑起来的》《自制搜索引擎》等。