请参与2024年Clojure状态调查,分享您的想法!2024 State of Clojure Survey!

欢迎!请查看关于页面,了解有关如何使用本页面的一些更多信息。

+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表达式的地方使用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

如果能只需使用IFn在任何需要java.util.function.*的地方,那就太棒了!

+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 投票
by

数据请求

除开java.util.stream.Stream和现有JDK API的适配器,如new Thread(() -> doSomething(x)),还有哪些lambda/SAM库在Clojure中难以使用?

0
by
_评论者:jwhitlark_


;; 我从一些关于kafka streams的调试代码中找到了这个。原始代码中全部用java8 lambda表达式填充了所有的reify。

;; 我会再找一个示例,展示使用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
            ;; 此处需要是 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 等类的对象。

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

由 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- 创建接口的代理实例,该方法通过调用 Clojure 函数执行方法句柄。它不会尝试验证参数个数,只是将其作为变长参数委托给 ISeq.applyTo。不是很确定这是否最高效,但对我的需求来说很有效。

我认为(链接: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 扩展到 Clojure 函数中,具有明确定义的 0 个参数。该接口在实际中变得越来越常见;这可能是很有价值的。我们不应该(也无法)对 defrecords 做同样的事情,因为它们已经有一个名为 get 的方法,这与 Supplier 的唯一方法相冲突。

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