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

欢迎!请参阅关于页面以获取更多关于如何使用本站的信息。

+57
Java 互操作
已封闭

将基准迁移到Java 8使我们能够考虑与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 投票
by

以下是使用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)))
by
恭喜并在Clojure 1.12中实现了这一点,谢谢
https://clojure.atlassian.net/browse/CLJ-2799
+3 投票
by

评论者:jwhitlark

如果我可以到处都使用IFn代替java.util.function.*,那就太好了!

+2 投票
by

评论由: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 投票

有人提出了一种很好的补丁来解决此问题,它简单地为 Clojure FN 做了 Java 用于 lambda 表达式所做的操作。

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

糟糕,最好为 CLJ-2637 分配一个“问题”,以免在这里的评论在两种方法之间变得混乱。

仅在遇到这个问题时,我想提供一些使用和背景信息。

在通过使用 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-)。
by

请求数据

暂不提及`java.util.stream.Stream`,以及现有JDK API的重构,比如`new Thread(() -> doSomething(x))`,有哪些使用lambda/SAM的库在Clojure中使用起来比较困难?

0个投票
by
_评论由:jwhitlark_发布


;; 我从实验kafka streams的一些临时代码中整理了这个。原始代码中补全了所有的reify的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<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个投票

评论者:gshayban

《Java 语言规范》(JLS)通过搜索与“单抽象方法”类(即功能接口)匹配的类(无论是接口还是抽象类)来推断lambda类型。我们可以有一个类似的辅助函数来检测这些类(链接: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) 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) 文件)库中提供了一个替代的方法。它使用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](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandleProxies.html#asInterfaceInstance-java.lang.Class-java.lang.invoke.MethodHandle-) 创建接口的代理实例,该实例调用方法句柄调用的Clojure函数。它不尝试验证参数个数,只是将其作为varargs代理到IFn.applyTo(ISeq)。虽然不确定这是否是最有效的方法,但对我来说是有效的。

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

我与我的方法(以及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的方法,这与方法单一的get方法冲突。)

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