尽管 Clojure 如此强调函数范式,为什么不使用Maybe
/ Option
monad 来表示可选值?在 Scala 中使用Option
非常普遍,这是一种我经常使用的函数式编程语言。
7 回答
Clojure 不是静态类型的,因此不需要在 haskell(我收集到,Scala)中必需的严格的 this/that/whatever 类型声明。如果要返回字符串,则返回字符串;如果你返回 nil ,那也没关系。
“功能”并不完全对应于“严格的编译时类型”。它们是正交概念,Clojure 选择动态类型。事实上,在相当长的一段时间里,我无法想象如何实现许多高阶函数,比如map
仍然保留静态类型。现在我对 Haskell 有一点(非常少)的经验,我可以看到它是可能的,而且确实通常非常优雅。我怀疑如果您使用 Clojure 一段时间,您将获得相反的体验:您会意识到类型声明对于赋予您在函数式语言中习惯拥有的那种力量并不是必需的。
在 Clojure 中,nil punning 提供了 Scala 和 Haskell 从 Option & Maybe 获得的大部分功能。
**Scala** **Clojure**
Some(1) map (2+_) (if-let [a 1] (+ 2 a))
Some(1) match { (if-let [a 1]
case Some(x) => 2+x (+ 2 a)
case None => 4 4)
}
Scala 的 Option 和 Haskell 的 Maybe 都是 Applicative 的实例。这意味着您可以在推导中使用这些类型的值。例如,Scala 支持:
for { a <- Some(1)
b <- Some(2)
} yield a + b
Clojure 的 for 宏提供了对 seq 的理解。与一元推导不同,此实现允许混合实例类型。
尽管 Clojure 的 for 不能用于在多个可能的 nil 值上组合函数,但它的功能实现起来很简单。
(defn appun [f & ms]
(when (every? some? ms)
(apply f ms)))
并称它为:
(appun + 1 2 3) #_=> 6
(appun + 1 2 nil) #_=> nil
重要的是要记住 Monad 的概念与类型无关!类型系统可以帮助您强制执行规则(但即使是 Haskell 也无法强制执行所有规则,因为其中一些规则(Monad Laws)无法由类型系统完全表达。
Monad 是关于组合的,这是我们每天在每种编程语言中都会做的一件非常重要的事情。在所有情况下,Monad 都会跟踪一些关于正在发生的事情的“额外上下文”......把它想象成一个保存当前值的盒子。函数可以应用于这个值,额外的上下文可以演变为正交关注点。
Maybe 类型是关于将长序列的计算链接在一起,而不必说任何关于失败的事情(这是“额外的上下文”)。这是一种将“错误处理”从计算中移到 Monad 中的模式。您可以在 Maybe 上进行一系列计算,一旦失败,其余的将被忽略,最终结果为“无”。如果他们都成功了,那么你的最终结果就是持有结果值的 monad。
这使您可以编写更少纠缠的代码。
正如@deterb 所指出的,Clojure 支持 Monads。
Maybe/Option 是一种类型。它与函数式编程无关。是的,一些语言(Scala、haskell、ocaml)除了功能之外还提供了非常强大的类型系统。人们甚至说 haskell 是一个 WITH TYPES 编程。
其他的(clojure、lisp)在类型方面没有提供太多,即使它们是完全有能力的函数式语言。它们的重点不同,Maybe/Option 类型不适合。它根本不会给你太多动态语言。例如,许多在序列(列表、向量、映射)上运行的 clojure 函数将完全接受 null (nil) 并将其视为空结构。
(count nil) 会给你 0。就像 (count [])
Clojure 不能被称为“使用类型编程”,因此 Maybe 类型在其中没有多大意义。
使用@amalloy 并评论说 Clojure 作为一种语言不需要可选的返回值。
我没有用 Scala 做太多事情,但是 Clojure 不需要知道关于返回类型的严格细节就能处理一个值。就好像一个 Maybe monad 被嵌入并成为正常 Clojure 评估的一部分,因为许多操作,如果在 上执行nil
,返回nil
。
我快速浏览了 Clojure-Contrib 库,他们有一个您可能想要查看的monad 包。另一个真正让我了解如何在 Clojure 中使用 Monad 的项目是Cosmin 关于 Clojure 中 Monads 的教程。正是这一点帮助我将在 Scala 中更明确地陈述的功能作为动态 Clojure 语言的一部分来处理。
从 Clojure 1.5 开始就有some->
并且可用some->>
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(some-> input :c :f :h inc))
user> 8
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(some-> input :c :z :h inc))
user> nil
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(-> input :c :z :h inc))
user> NullPointerException clojure.lang.Numbers.ops (Numbers.java:1013)
该some->
函数提供 Option[T]。