计算一个数的绝对值是非常基础的操作,几乎所有主流的编程语言都内置了相应的函数或方法。
在 PHP、Python、SQL 等语言中,直接调用 abs()
函数即可,例如 abs(-1)
。到了 Java、C# 这类面向对象的语言中,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
才不合理。
没有绝对的对错,只有不同的选择。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。