请在2024年Clojure状态调查!分享您的想法。

欢迎!有关本网站的工作方式,请参阅关于页面以获取更多信息。


将Java 8作为基准可以让我们考虑与诸如Function、Predicate、Supplier等关键的java.util.Function接口的内置绑定。需要对用户能够实现什么以及哪些自动集成进行需求评估。

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

已关闭,备注为:已发布在Clojure 1.12.0-alpha12中
为什么我们在2023年仍然没有任何官方的回应,关于何时将实现或解决这一功能?
我们已经对这个问题进行了不定期的研究,目前已经有很多方面需要考虑,并且已经有几种推进方案,其中我们已为几个方案构建了原型,但我们还没有决定要做什么。这个功能在我们的1.12版本范围列表中。

这是用Java编写的Kafka Streams应用的样子

      sb.table("input", Consumed.with(sl, sl))
            .groupBy((k, v) -> KeyValue.pair(k / 10, v), Grouped.with(sl, sl))
            .aggregate(() -> 0L,
                    (k, v, acc) -> acc + v,
                    (k, v, acc) -> acc - v,
                    Materialized.with(sl, sl))
            .toStream()
            .to("output", Produced.with(sl, sl));

同样用Clojure编写的应用看起来是这样的

    (-> sb
        (.table "input" (topic->consumed data-in))
        (.groupBy (key-value-mapper
                    (fn [k v] (KeyValue/pair (long (/ k 10)) v)))
                (serdes->grouped "groupie" data-in))
        (.aggregate (reify Initializer
                    (apply [_] 0))
                    (reify Aggregator
                    (apply [_ k v acc]
                        (+ acc v)))
                    (reify Aggregator
                    (apply [_ k v acc]
                        (- acc v)))
                    (serdes->materialised ...))
        (.toStream)
        (.to "output" (topic->produced data-out)))

如果我们能够在期望SAM类型的地方使用Lambda表达式,我们就可以用这种方式来编写代码

    (-> sb
        (.table "input" (topic->consumed data-in))
        (.groupBy (fn [k v] (KeyValue/pair (long (/ k 10)) v))
                (serdes->grouped "groupie" data-in))
        (.aggregate (constantly 0)
                    (fn [k v acc] (+ acc v))
                    (fn [k v acc] (- acc v))
                    (serdes->materialised ...))
        (.toStream)
        (.to "output" (topic->produced data-out)))
恭喜并感谢您在Clojure 1.12中实现了这个功能
https://clojure.atlassian.net/browse/CLJ-2799
+3

由:jwhitlark发表的评论

如果只要能在需要java.util.function.*的地方使用IFn,那将会很棒!

+2

由:marctrem发表的评论

将基准Java版本移至Java 8,使我们能够使用默认接口方法。

some-java-fns-interface.patch补丁在IFn上实现了Consumer、Function、Predicate和Supplier。

如果您想走这条路,我将非常愿意在IFn上实现java.util.function下的所有接口及相关测试。目前我使用这段代码通过其Java客户端来玩转FoundationDB,对我来说效果很好。

https://github.com/marctrem/clojure/commit/97742493f674edd8f6c034ee94da84fa62a76bad

+2

有人提供了一个很好的补丁来解决这个问题,这是我见过的最好的解决方案,它仅仅是做了Java为lambdas所做的,但它是为Clojure FN做的。

https://clojure.atlassian.net/plugins/servlet/mobile?originPath=/browse/CLJ-2637#issue/CLJ-2637

糟糕,我们最好为CLJ-2637分配一个“问题”,以免这里的评论在这两种方法之间纠缠不清。
+1 投票

我刚刚遇到这个问题,因此我想提供一些使用/上下文信息。

在与使用CompletableFutureCompletionStage编写的异步Java代码接口时,需要提供实现FunctionConsumerBiFunction和其他功能的参数。

我正在使用这样的宏

(defmacro as-function [f]
  `(reify java.util.function.Function
     (apply [this arg#]
       (~f arg#))))

(defmacro as-consumer [f]
  `(reify java.util.function.Consumer
     (accept [this arg#]
       (~f arg#))))

但是,由于需要根据函数参数的不同而创建变体,因此这很快变得繁琐。

java.util.function定义了大量的接口,但根据我的(有限)观点,我需要最常用的接口,尤其是那些由CompletionStageCompletableFuture要求的。

此外,还需要使用基本的 Java 接口,如 java.util.Map。为了最有效地使用并发哈希表,您需要使用 compute、computeIfPresent、computeIfAbsent - (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#compute-K-java.util.function.BiFunction-)。
+1 投票

数据请求

除了 java.util.stream.Stream,以及现有 JDK API 的重构,如 new Thread(() -> doSomething(x)) 之外,还有哪些是难以从 Clojure 中使用的 lambda/SAM 库的示例?

0
_评论由:jwhitlark_制


;; 我从一些 kafka streams 的实验代码中挖出了这个。原始的 reify 实例都是用 java8 lambdas 填充的。

;; 我会找到另一个使用 java.utils.funstion.* 中内容的示例。

;; 一些这是从 franzy 示例或其他地方学来的?

;; 注意,例如,
;; https://kafka.apache.org/0102/javadoc/org/apache/kafka/streams/kstream/Predicate.html
;; 与
;; https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html

(ns utils
  (:import (org.apache.kafka.streams.kstream Reducer KeyValueMapper ValueMapper Predicate))

(defmacro reducer [kv & body]
  `(reify Reducer
     (apply [_# ~(first kv) ~(second kv)]
       ~@body)))

;; public interface KeyValueMapper
;; apply(K key, V value)
(defmacro kv-mapper [kv & body]
  `(reify KeyValueMapper
     (apply [_# ~(first kv) ~(second kv)]
       ~@body)))

;; public interface ValueMapper
;; apply(V1 value)
(defmacro v-mapper [v & body]
  `(reify ValueMapper
     (apply [_# ~v]
       ~@body)))

(defmacro pred [kv & body]
  `(reify Predicate
     (test [_# ~(first kv) ~(second kv)]
       ~@body)))

;; 我用它之类的这样

(ns our-service.kafka-streams
  (:require
   [our-service.util :as k]
   [clojure.string :as str]
  (:import
           (org.apache.kafka.streams StreamsConfig KafkaStreams KeyValue)
           (org.apache.kafka.streams.kstream KStreamBuilder ValueMapper)))

(defn create-word-count-topology []
  (let [builder (KStreamBuilder.)
        init-stream (.stream builder (into-array ["streams-str-input"]))
        wc (-> init-stream
             (.flatMapValues (k/v-mapper [& value]
                                              `(str/split (apply str value) #"\s")))
            (.map (k/kv-mapper [k v]
                                            (KeyValue/pair v v))))
            (.filter (k/pred [k v]
                             (println v)
                             (not= v "the")))
            (.groupByKey)
            (.count "CountStore")
            显示项目
            ;; 这需要mapValues
            (.mapValues (reify ValueMapper
                          (apply [_ v]
                            (println v)
                            (str v))))
            (.toStream)
            (.to "wordcount-output"))]
    [builder wc]))
0

评论者:gshayban

JLS通过寻找匹配的功能接口,即所谓的“单抽象方法”类(链接:1)(无论是接口还是抽象类)来推断lambda类型。我们可以有一个类似reify的辅助程序来检测这些类(链接:2)。您需要提示目标类。我们实际上不需要既是IFn又是j.u.f.Predicate等的东西。

`
(导入 '[java.util.function Predicate Consumer])

(设置 [orig [1 2 3]

  st (atom [])]

(.forEach orig (jfn Consumer [x] (swap! st conj x)))
(= @st orig))
`

(链接:1) https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8
(链接:2) 链接 https://gist.github.com/ghadishayban/0ac41e81d4df02ff176c22d16ee8b972

0

由:jwhitlark发表的评论

嗯,这将是一个改进。我在实践中遇到的实际问题是,我经常深入到流畅的接口中,并不知道确切的类。话虽如此,这通常只在少数地方。是否应该有一个注册表?也许像这样

(auto-infer-lambda (链接:java.util.function,org.apache.kafka.streams.kstream))

0

评论者:gshayban

您是否使用过抽象类而不是接口的SAM类?

0

评论者:ajoberstar

以下是在我(链接:[在这里](https://github.com/ajoberstar/ike.cljj/blob/master/src/main/clojure/ike/cljj/function.clj) 文本:ike.cljj)库中的另一种方法。它使用MethodHandles(即java.lang.invoke包)而不是常规反射。我不确定我是否在抽象类上测试过这个方法。

用法看起来与Ghadi发布的类似

`
(defsam my-sam
java.util.function.Predicate
[x]
(= x "it matched"))

(-> (Stream/of "not a match" "it matched")

(.filter my-sam)
(.collect Collectors/toList)

(-> (IntStream/range 0 10)

(.filter (sam* java.util.function.IntPredicate odd?))
(.collect Collectors/toList)

`

它使用[这里](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandleProxies.html#asInterfaceInstance-java.lang.Class-java.lang.invoke.MethodHandle-) 文本:MethodHandleProxies.asInterfaceInstance) 创建一个代理实例的接口,该实例调用一个Clojure函数的方法。它不尝试验证参数数量,它只是将其作为varargs委托给IFn.applyTo(ISeq)。不确定它是否是最有效的,但它对我来说是有效的。

我认为[这里](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/invoke/MethodHandles.html) 的LambdaMetaFactory可能是满足这种用例的首选方法。但我发现要确切地了解如何使用它有困难,所以我没有深入研究。

我在我的方法(以及Ghadi的方法)中有主要的函数问题,你必须明确地提供你要代理的接口。Java lambdas和Groovy Closures可以针对期望SAM的方法使用,它只是根据方法期望的内容进行转换。理想情况下,这将由Clojure支持。

而不是必须这样做:

`
(-> (IntStream/range 0 10)

(.filter (sam* java.util.function.IntPredicate odd?))
(.collect Collectors/toList)

`

我想这样做:

`
(-> (IntStream/range 0 10)

(.filter odd?)
(.collect Collectors/toList)

`

0

评论者:gshayban

另一个可能的方法是将java.util.function.Supplier扩展为具有显式0个参数的Clojure函数。这个接口在实践中变得越来越普遍;这可能是有价值的特殊情况。(我们不应该也不能为defrecords做同样的事情,因为它们已经有了get方法,这会与Supplier的唯一方法冲突。)

0
参考:[这里](https://clojure.atlassian.net/browse/CLJ-2365)(由alexmiller报告)
...