clojure 编辑器
本周,主题是换能器。 但是在进入该主题之前,我们首先需要更多地讨论减速器。
这是在学习Clojure的焦点series.Other职位包括8 个职位:
- 解码Clojure代码,让您不知所措
- 学习Clojure:应对动态打字
- 学习Clojure:arrow和doto宏
- 学习Clojure:动态调度
- 学习Clojure:依赖类型和基于合同的编程
- 学习Clojure:与Java流进行比较
- 关于学习Clojure的反馈:与Java流进行比较
- 学习Clojure:换能器 (本文)
Java减少
如果您有Java 8的经验,您可能已经了解Stream.reduce()
函数。 提供三种不同的口味:
-
Optional<T> reduce(BinaryOperator<T> accumulator)
-
T reduce(T identity, BinaryOperator<T> accumulator)
-
<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
用法示例如下:
int[]ints=newint[]{0,1,2,3,4};
OptionalIntsum1=Arrays.stream(ints).reduce(Integer::sum); (1)
intsum2=Arrays.stream(ints).reduce(0,Integer::sum); (2)
- 没有初始值,得出10
- 初始值也为10
注意,第一风味剂和第二风味剂之间的唯一区别是初始值的存在。 这意味着,第二香味被绑定到返回一个值, 即使该流不包含任何元素 ,即 OptionalInt
VS int
。
第三种口味的唯一用例是并行处理,因此我们将其放在一边。
减少Kotlin
Kotlin中的还原与Java中的还原非常相似。 开箱即用提供了两种简化功能:
- 一个没有初始值的文件,名为
reduce()
- 另一个具有初始值,称为
fold()
valints=arrayOf(0,1,2,3,4)
valsum1=ints.reduce(Int::plus)
valsum2=ints.fold(0,Int::plus)
返回不相关的类型
实际上,Java和Kotlin的缩减都非常有限:返回的类型表示单个值。 减少(也称为倍数)的定义要广泛得多:
在函数式编程中,折叠(也称为减少,累加,聚集,压缩或注入)是指一系列高阶函数,这些函数分析递归数据结构,并通过使用给定的组合运算来重组递归处理其结果的结果。组成部分,建立回报价值。 通常,折叠具有组合功能,数据结构的顶部节点以及可能在某些条件下使用的某些默认值。 折叠然后继续以系统的方式使用功能组合数据结构层次结构的元素。
https://zh.wikipedia.org/wiki/Fold_(高级功能)
此定义对结果类型没有任何要求。 由于集合的类型与其他类型相同,因此以下功能也有所减少:
funreduce(acc:Set<Int>,ints:List<Int>):Set<Int>=acc+ints
funreduce(acc:Set<Int>,aInt:Int):Set<Int>=acc+aInt
funreduce(aInt:Int):Set<Int>=setOf(aInt)
funreduce(ints:List<Int>):Set<Int>=setOf<Int>()+ints
归纳归约
在这个阶段,下一步是很小的。 让我们考虑以下代码:
ints.distinct()
这与上面的最后一个功能相同。 考虑到更广泛的定义, distinct()
也是归约函数。
同样,以下两个代码段相同:
funreduce(ints:List<Int>):List<Int>{
vallist=mutableListOf<Int>()
ints.forEach{list.add(it+1)}
returnlist
}
ints.map{it+1}
因此, map()
是归约函数。 filter()
符合定义。 更多功能也符合上面的描述。
换能器
现在已经正确定义了归约函数,现在可以定义换能器是什么:
换能器(有时称为xform或xf )是从一种归约函数到另一种归约函数的转换 。
https://clojure.org/reference/transducers
归约函数的组成与此相符。 现在已经定义了换能器,现在该是Clojure了。 实际上,Clojure使定义换能器变得容易。
让我们从使用thread-last宏定义归约函数管道开始:
(defn transform [coll]
(->> coll (1)
(filter even?) (2)
(take 5) (3)
(map inc))) (4)
提醒一下,这是分步说明:
- 从
coll
开始-假设一组数字 - 仅保留偶数
- 仅保留前5个数字
- 每个数字加一
组成功能
下一步是使用允许组成函数的(comp)
函数:
(comp)(comp f)(comp fg)(comp fg & fs)
接受一组函数并返回一个由这些fns组成的fn。 返回的fn采用可变数量的args,将fns的最右边应用于args,将下一个fn(从右至左)应用于结果,依此类推。
https://clojuredocs.org/clojure.core/comp
为了从管道创建合成函数,使用(comp)
函数。
(def transducer
(comp
(filter even?)
(take 5)
(map inc)))
创建换能器
重要的一点是,尽管(transform)
和(transducer)
函数实现看起来相同,但它们却不同。
在thread-last的上下文中, 即 ->>
,该函数在“管道”点处应用于结果集合。
(--> (range 25)
(filter even?)
(take 5)
(map inc))
相反,在(transducer)
函数的上下文中, (filter even?)
仅使用单个参数执行(filter)
函数。 回到(filter)
的定义:
(filter pred)(filter pred coll)
返回coll中项目的惰性序列,
(pred item)
为其返回逻辑true
。pred
必须没有副作用。 没有提供任何收集时返回一个传感器。
https://clojuredocs.org/clojure.core/filter
使用REPL可以很容易地验证最近的要求:
(filter even?)
=> #object[clojure.core$filter$fn__5610 0x261832c7 " [email protected] "]
实际上,在不提供任何集合的情况下,许多处理集合的现成的Clojure函数也会返回一个换能器。 这不仅包括到目前为止使用的功能(filter)
, (take)
和(map)
,还包括:
-
(distinct)
-
(drop)
-
(map-indexed)
-
(mapcat)
-
(partition-by)
-
(random-sample)
-
(remove)
- 等等
本要点中提供了核心中提供的换能器的详尽列表。
应用换能器
要将换能器应用于集合,Clojure提供了(transduce)
功能:
(transduce xform f coll)(transduce xform f init coll)
用
f (xf)
的变换来减少。 如果未提供init
,则将调用(f)
生成它。f
应该是一个接受1和2参数的归约步骤函数,如果它仅接受2,则可以将arity-1添加为'completing'。 返回将(转换后的)xf
应用于init
和coll中的第一项的结果,然后将xf
应用于该结果和第二项,以此类推。
https://clojuredocs.org/clojure.core/transduce
由于形式上的定义可能有点让人不知所措,因此以下是使用(transducer)
的示例:
(transduce
(take 5) (1)
conj (2)
(range 25)) (3)
- 创建一个采用前5个元素的换能器
- 将
(take 5 (range 25))
简化为向量 - 初始集合
与最后线程宏相比,有两个重要区别:
- 换能器是高阶函数 。 它可用作参数和返回值。
-
(transducer)
需要一个附加参数,并允许第二个参数。 必选参数是归约函数,而可选参数是初始值。
结论
使用换能器,可以定义一个命名的折减管道。 它们允许独立于任何源或目标来定义转换或转换的有序管道。
但是,换能器远不止于此:有无状态换能器和有状态换能器。 同样, (transduce)
只是在Clojure中应用换能器的一种方法,但还有其他方法。
这只是换能器的第一步。
clojure 编辑器