请在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表达式的地方使用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客户端来玩FoundaoDB,并且对我的测试来说效果很好。

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))之外,还有哪些lambda/SAM库是从Clojure中使用时感觉不太方便的实例?

0
评论者:jwhitlark


;; 我从这个用于实验kafka streams的的一些草稿代码中挖出了一个例子。原始的所有reify都被填充了用于java8 lambda的例子。

;; 我会再挖出一些使用java.utils.funstion.*库的例子。

;; 这部分内容是从某个franjy的例子中通过的?

;; 注意,例如:
;; 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")
            显示项目
            ;; 这个需要是mapValues
            (.mapValues (reify ValueMapper
                          (apply [_ v]
                            (println v)
                            (str v))))
            (.toStream)
            (.to "wordcount-output"))]
[[构建器 wc]))
0
by

评论人:gshayban

JLS 通过搜索匹配的函数式接口(又称“单一抽象方法”类)推断 lambda 类型(链接:1)(无论它们是接口还是抽象类)。我们可以有一个类似于 reify 的辅助函数来检测这些类(链接:2)。您需要提示目标类。我们并不真的需要同时是 IFnj.u.f.Predicate 等的东西。

`
(导入 '[java.util.function Predicate Consumer])

(设 [1 2 3] 为 orig)

  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作出

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

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

0
by

评论人: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 函数。它不会尝试验证参数数量,只是将其视为 varargs,委托给 IFn.applyTo(ISeq)。不确定它是否是最有效的,但对于我的需求来说效果不错。

我认为(链接::https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/invoke/MethodHandles.html 文本:LambdaMetaFactory)可能是满足此类用例的首选方式。不过,我难以理解如何精确使用它,所以我没有深入研究。

我对我这种方法(以及 Ghadi 的方法)的主要功能问题是,你必须明确提供要代理的接口。Java 箭头和 Groovy 闭包可以用于期望 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 提出)
...