Clojure China

Why `(:key nil)` returns nil rather than throws an error?

#1

There’s nothing in nil

#2

我认为这是 FPOO 的根本区别:

FP 一般来说是 nil 友好的, give me nothing, return nothing
而且对于链式调用也非常有好处

(-> x :a :b :c)

对待 nil 上面, OO 有两个麻烦问题

  1. nil 是不是对象?(FP中, nil 只是数据而已)
  2. 方法是跟对象绑定的。
    如果 nil 是对象,那么 nil 绑定了那些方法; 如果不是, 那为什么允许 nil 能赋值给变量。
    所以 Java 就折衷, 不是对象, 但是允许赋值给变量。所以就有了著名的 NullPointerException
#3

Haskell 里会为这种场景专门用 Maybe Monad 去处理, 那样严谨多了. 而开发时候我经常遇到写了 (:key x)xnil 很难查出来.

#4

我觉得这个问题是存在的。不少时候,让 nil 检查 进入程序逻辑,显得累赘;而 (:key nil) 不导致异常这样可能来自动态类型的特性,却让手动 nil检查 越发必要。也许即将引入的 clojure.spec 能帮得上忙 @~@

其他情况下,nil 是程序逻辑的一部分,那么不抛出异常的行为就是合理的。比如递归逻辑里的 base case。

如果需要在这种情况下抛异常,最直接的做法可能是用 (map-that-might-be-nil key) 替代 (key map-that-might-be-nil)这里 的讨论很棒: )

至于为什么,我还说不明白。。。

#5

语法糖带来争议还挺多的

#6

Java中null不是原生类型(如:int),也不是任何引用类型(如:Object)。它是没有名字(nameless)的类型,所以我们没法直接声明这样的变量(如:Null foo = null)。但是比较奇葩的设计是Java允许将null赋给任意的引用类型。

这样null在OO的世界成为一种奇怪的东西:它不是对象。它算是一种虚无的存在(又虚无又存在,悖论哈),指示了对象未诞生时的状态,一旦对象生成,null就被抹去了。所以Martin Fowler才会建议使用Null Object来表达这种临时状态在OO中的模样。所谓的Null Object就是这样一种对象——啥事不干的对象。听起来很神奇,其实说得过去,你想啊,Null Object只是表征了要么有值,要么没值这个选择(Optional)的一个特例,真没啥行为。

这又引出了Optional的概念。正如jiyinyiyong所言, Haskell使用了 Maybe Monad 来包装了这种可选的状态(这就是一组表示可选的计算)Java 8中也使用Optional这个类实现了类似的功能(提供了flatmap、of以及map等方法,活脱脱的一个monad嘛)。

回到Clojure中,nil显然有别于Java中的null。首先nil是数据也是类型(为哈? nil可以extend protocol啊),它可不是Clojure中的异类,它是一等公民!其次,nil在不同的上下文中具有不一样的意义,比如(:key nil) ;=> nil, (first '()) ;=> nil, (str nil) ;=> “”,这里它表示了nothing、空数组、空字符串,当然还有false了。

所以你无法将nil对应到OO中的Null Object、也无法对应到Pure FP中Maybe monad,它是Clojure中一支双关语(nil-punning),用得好舒心,用得不好也会很头疼。

1赞