可以更好地避免 nil 连接吗?
Can avoiding nil conjoins be done better?
我有一个令牌扫描器,它只是 returns nil
我不感兴趣的字符。而不是 conj
nils 到我的令牌向量,然后再将它们全部剥离出来,我只想不添加它们。
我正在使用
;; dont conjoin if value false
(defn condj [v val]
(cond-> v, val (conj val)))
做到这一点。有没有具体的算子或者更简洁的实现?
这里我认为过于简化的做法是最干净的(也稍微简洁一些):
(defn condj [v val]
(if val (conj v val) v))
我发现这更容易快速理解。唯一的缺点是 v
是重复的,因为它没有被线程化,但是对于这样一个简单的函数来说,这并不是一个很大的损失。
我喜欢 cond->
版本并且经常使用它来避免在 if
版本中重复。不过,不要忘记明确说明 false
值。我还喜欢使用 Plumatic Schema 明确表示进入和离开函数的数据形状:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]))
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-> accum
(not (nil? item)) (conj item)))
(dotest
(let [result (-> []
(condj :a)
(condj :2)
(condj false)
(condj "four")
(condj nil)
(condj "last"))]
; nil is filtered but false is retained
(is= result [:a :2 false "four" "last"])))
您可能还对使用 my favorite library 的另一个版本感兴趣:
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-it-> accum
(not-nil? item) (append it item)))
对于更复杂的表单,使用占位符符号 it
可以明确显示值的线程位置。我也喜欢便利函数 not-nil?
和 append
,因为它们也使代码的意图更清晰。
我相信您可以为此使用 transducers。它们被解释 here。我们的归约函数是 conj
并且我们构建了一个变换器 (remove nil?)
将此函数转换为忽略 nil
:
的函数
(def condj ((remove nil?) conj))
注意remove
与filter
相反。我们还可以使用 (filter some?)
实现 condj
,some? 是一个对除 nil
:
以外的任何值都成立的函数
(def condj ((filter some?) conj))
似乎有效:
user=> (condj [3 4 5] 9)
[3 4 5 9]
user=> (condj [3 4 5] nil)
[3 4 5]
user=> (condj [3 4 5] false)
[3 4 5 false]
考虑使用 into
而不是 conj
:
(into [1 2 3] nil) ;=> [1 2 3]
(into [1 2 3] [4]) ;=> [1 2 3 4]
注意:缺点是您必须 return 产生一个序列,这会增加一些开销。但是,如果您忘记执行此操作,则会出现错误,当您想要附加多个项目时,可以轻松扩展逻辑,代码易于理解,并且不需要创建任何自定义函数。
我有一个令牌扫描器,它只是 returns nil
我不感兴趣的字符。而不是 conj
nils 到我的令牌向量,然后再将它们全部剥离出来,我只想不添加它们。
我正在使用
;; dont conjoin if value false
(defn condj [v val]
(cond-> v, val (conj val)))
做到这一点。有没有具体的算子或者更简洁的实现?
这里我认为过于简化的做法是最干净的(也稍微简洁一些):
(defn condj [v val]
(if val (conj v val) v))
我发现这更容易快速理解。唯一的缺点是 v
是重复的,因为它没有被线程化,但是对于这样一个简单的函数来说,这并不是一个很大的损失。
我喜欢 cond->
版本并且经常使用它来避免在 if
版本中重复。不过,不要忘记明确说明 false
值。我还喜欢使用 Plumatic Schema 明确表示进入和离开函数的数据形状:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]))
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-> accum
(not (nil? item)) (conj item)))
(dotest
(let [result (-> []
(condj :a)
(condj :2)
(condj false)
(condj "four")
(condj nil)
(condj "last"))]
; nil is filtered but false is retained
(is= result [:a :2 false "four" "last"])))
您可能还对使用 my favorite library 的另一个版本感兴趣:
(s/defn condj :- [s/Any]
"Conjoin an item onto a vector of results if the item is not nil."
[accum :- [s/Any]
item :- s/Any]
(cond-it-> accum
(not-nil? item) (append it item)))
对于更复杂的表单,使用占位符符号 it
可以明确显示值的线程位置。我也喜欢便利函数 not-nil?
和 append
,因为它们也使代码的意图更清晰。
我相信您可以为此使用 transducers。它们被解释 here。我们的归约函数是 conj
并且我们构建了一个变换器 (remove nil?)
将此函数转换为忽略 nil
:
(def condj ((remove nil?) conj))
注意remove
与filter
相反。我们还可以使用 (filter some?)
实现 condj
,some? 是一个对除 nil
:
(def condj ((filter some?) conj))
似乎有效:
user=> (condj [3 4 5] 9)
[3 4 5 9]
user=> (condj [3 4 5] nil)
[3 4 5]
user=> (condj [3 4 5] false)
[3 4 5 false]
考虑使用 into
而不是 conj
:
(into [1 2 3] nil) ;=> [1 2 3]
(into [1 2 3] [4]) ;=> [1 2 3 4]
注意:缺点是您必须 return 产生一个序列,这会增加一些开销。但是,如果您忘记执行此操作,则会出现错误,当您想要附加多个项目时,可以轻松扩展逻辑,代码易于理解,并且不需要创建任何自定义函数。