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)))

如果我们能使用Lambdas并且期望SAM类型的位置,我们就可以这样写

    (-> 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功能(
+1
by

数据请求

除了java.util.stream.Stream和现有JDK API的retrofits,例如new Thread(() -> doSomething(x)),还有一些使用lambda/SAM的库难以从Clojure中使用吗?

0
by
评论者: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]
                                        (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”)]
([builder wc]))
0

由 gshayban 发布的评论

JLS 通过寻找匹配的功能接口(即“单抽象方法”类)来推断 lambda 类型(链接:1)(无论是接口还是抽象类)。我们可以有一个类似reify的帮助程序来检测这些类(链接: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 发布的评论

以下提供了一种替代方法(链接: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

由 gshayban 发布的评论

另一个可能的途径是将 `java.util.function.Supplier` 扩展到具有显式 0-arity 的 Clojure 函数中。该接口在现实中变得越来越常见;可能值得对其进行特殊处理。(我们不应也没法为 `defrecords` 做类似的事情,因为它们已经有了一个名为 `get` 的方法,它与 `Supplier` 的唯一方法冲突。)

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