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

如果能在不同地方使用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。

为了最有效地使用并发HashMap,您需要使用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.*中的内容的示例。

;;这些建议中有一部分是从一个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

Java语言规范(JLS)通过寻找匹配的功能接口,也称为“单抽象方法(SAM)类”推断lambda类型(链接:1)(无论它们是接口还是抽象类)。我们可以有一个类似reify的辅助工具来检测这些类(链接:2)。您需要提示目标类。我们并不真正需要同时是IFnj.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)
`

(链接: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

这将是改进。我遇到的实际问题是,我经常会沉浸在流畅的接口中,不一定知道确切的类。话虽如此,这通常只在少数几个地方。是否应该有一个注册表?或许像这样

(自动推断 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扩展为Clojure函数,带有显式的0个参数。这种接口在实践中变得越来越常见;这可能非常有价值来特别处理。(我们不应该也不能类似地为defrecords做这件事,因为它们已经有了名为get的方法,这与Supplier的唯一方法冲突。)

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