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

欢迎!有关此功能的更多信息,请参阅关于页面。

+57
Java Interop
已关闭

将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为lambda做的操作,但它是为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.* 中内容示例的例子。

;; 有一部分是从 franz 示例或其他地方搬过来的吗?

;; 注意,例如,
;; 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<K,V,R>
;; apply(K key, V value)
(defmacro kv-mapper [kv & body]
  `(reify KeyValueMapper
     (apply [_# ~(first kv) ~(second kv)]
       ~@body)))

;; public interface ValueMapper<V1,V2>
;; 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
            ;; 这是一个需要mapValues的例子
            (.mapValues (reify ValueMapper
                          (apply [_ v]
                            (println v)
                            (str v))))
            (.toStream)
            (.to "wordcount-output")

    [建造者 wc]))
0 投票
jim-stornesc 在2018年7月10日 创建 回答

评论者:gshayban

JLS通过寻找匹配的函数接口,也就是所谓的“单抽象方法”类(link: 1)来推断lambda类型(无论是接口还是抽象类)。我们可以有一个类似于reify的辅助函数来检测这些类(link: 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))
`

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

0 投票
回答

评论者:jwhitlark

嗯,这将是一个改进。我在遇到的实际问题是,我经常深陷在流畅的接口中,不一定知道确切的类。话虽如此,这通常只在几个地方。有一个注册表是否有意义?可能像这样

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

0 投票
回答

评论者:gshayban

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

0 投票
by

评论由: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函数。它不尝试验证参数数量,而是将其作为可变参数委托给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 投票
by

评论者:gshayban

另一种可能的方法是扩展java.util.function.Supplier以将Clojure函数与显式的0个参数数扩展到。这个接口在实践中的使用越来越广泛,可能很有价值进行特殊处理。(我们不应该也不可能为defrecords做类似的事情,因为它们已经有一个名为get的方法,这与Supplier的唯一方法冲突。)

0 投票
by
参考: https://clojure.atlassian.net/browse/CLJ-2365(由alexmiller提交)
...