Clojure 2024状态调查中分享您的想法!

欢迎!请访问关于页面以获取有关如何操作的更多信息。

+57
Java 互操作
已关闭

将基准移至Java 8允许我们考虑与关键java.util.Function接口(如Function、Predicate、Supplier等)的内建联系。需要对可能性和为用户打开的自动集成进行评估。

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

已关闭,备注为:在Clojure 1.12.0-alpha12中发布
为什么在2023年,我们还没有任何关于何时将实现或解决此问题的官方回复?
我们已经断断续续地关注这个问题好几个版本了。有多个方面要考虑,以及如何进行的多种选择,其中许多我们已经为它们构建了原型,但我们还没有决定做什么。它在1.12的范围内。

13 个答案

+4

这是用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类型的地方使用Lambdas,我们可以这样编写

    (-> 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 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)),还有哪些 lambda/SAM 使用库在 Clojure 中使用起来比较困难?

0 投票
_评论者:jwhitlark_


;; 我从一些实验 kafka streams 的 spare code 中找到了这一段。原始代码中所有 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")
            show-item
            ;; this needs to be 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]))

(let [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) spike https://gist.github.com/ghadishayban/0ac41e81d4df02ff176c22d16ee8b972

0 投票

评论作者:jwhitlark

好吧,这将是一个改进。我在实践中遇到的实际问题是,我经常深入到流畅接口中,不一定知道确切的类。话虽这么说,通常只在几个地方。有一个注册表是否合适?或许像这样

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

0 投票

评论者:gshayban

你有没有使用既是抽象类又是接口的SAM类?

0 投票

评论者:ajoberstar

这里是一个替代方法,在我的(ike.cljj)库中(链接:[https://github.com/ajoberstar/ike.cljj/blob/master/src/main/clojure/ike/cljj/function.clj](https://github.com/ajoberstar/ike.cljj/blob/master/src/main/clojure/ike/cljj/function.clj))。它使用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)

`

它使用MethodHandleProxies.asInterfaceInstance来创建一个代理实例的接口,该接口调用一个方法处理程序调用Clojure函数。它不会尝试验证参数数量,只是将它作为varargs委托给IFn.applyTo(ISeq)。不确定这是否最有效,但对我来说它是有效的。

我认为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以将Clojure函数与显式的0个参数(arity)结合起来。这个接口在实际应用中变得越来越普遍;它可能是有价值的特殊案例。(我们不应该也能做到这一点,对于defrecords,因为它们已经有了一个名为get的方法,它与Supplier的唯一方法相冲突。)

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