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

欢迎!有关此如何工作的更多信息,请参阅关于页面。

+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类型的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 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 lambda。

;; 我会再找出一个使用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]
                               (.pair v v)))
            (.filter (k/pred [k v]
                             (println v)
                                       (not= v "the"))
             (.groupByKey)
             (.count "CountStore")
             show-item
              这个需要转换为mapValues
             (.mapValues (reify ValueMapper
                            [(apply [_ v]
                              [println v)
                              [str v))))
             (.toStream)
             (.to "wordcount-output")]
        [builder wc]))
0

by

评论者:gshayban

JLS通过寻找匹配的函数接口(也称为“单抽象方法”类)来推断lambda类型(链接:1)(不论它们是接口还是抽象类)。我们可以有一个类似reify的辅助函数来检测这些类(链接:2)。您需要指定目标类。我们实际上不需要既有IFn又有j.u.f.Predicate等的类。

`
(import '[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

by

由jwhitlark发表的评论:

嗯,这将是一个改进。我在实践中遇到的实际问题是,我经常深入到fluently接口中,不一定知道确切的类。话虽如此,通常只有几个地方。是否有必要创建一个注册表?也许类似这样的东西

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

0

by

评论者:gshayban

你是否使用过抽象类而非接口的SAM类?

0

by

评论者:ajoberstar

这里提供了一个替代方案,在(ike.cljj)库中使用,可以在[链接](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)

`

它使用[链接](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandleProxies.html#asInterfaceInstance-java.lang.Class-java.lang.invoke.MethodHandle-)来创建一个代理实例,该实例通过调用Clojure函数的方法处理调用接口。它不尝试验证参数数量,而只是将其作为变长参数委派给IFn.applyTo(ISeq)。不知道这是否是最有效率的方法,但对我来说很有效。

我认为[链接](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/invoke/MethodHandles.html)可能是满足此类用例的首选方式。虽然我很难准确了解如何使用它,因此我没有深入研究。

我的主要功能问题是我的方法(以及Ghadi的)必须有明确提供的代理接口。Java Lambda和Groovy Closure可以对期望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报告)
...