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年还没有任何关于何时实施或处理此问题的官方回应?
Feb 17, 2023span>
我们已经对这个功能断断续续地研究了好几个版本。它有很多方面,有几种推进方案,我们为其中一些建了原型,但我们还没有决定要做什么。这是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表达式中使用,我们可以这样写

    (-> 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版本提升至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 投票
by

有人制定了一个补丁来解决这个问题,这是我看到过的最好的实现方式,它正好做了Java为lambdas所做的一切,但它为Clojure FN做了。

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

by
哎呀,我们最好为CLJ-2637预留一个“问题”,以免这里的评论在这两种方法之间变得混乱。
+1
by

刚刚发现这个(指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要求的接口。

by
此外,只是基本的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,以及像new Thread(() -> doSomething(x))这样的现有JDK API的重构,还有哪些lambda/SAM使用库使用 Clojure 时感觉笨拙?

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

  `(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]
                               (键值对/pair v v)))
            (.filter (k/预测 [k v]
                             (打印 v)
                             (not= v "the")))
            (.groupByKey)
            (.count "计数存储")
            show-item
            ;; 这需要是mapValues
            (.mapValues (在现实 [ValueMapper
                          (apply [_ v]
                            (打印 v)
                            (字符串 v))))
            (.toStream)
            (.to "wordcount-output"))]
    [构建器 wc]))
0

评论者:gshayban

JLS 通过寻找匹配的功能接口(也称为“单一抽象方法”类)来推断 lambda 类型(链接:1)(无论是接口还是抽象类)。我们可以有一个类似 reify 的辅助器来检测这些类(链接:2)。您需要指定目标类。我们实际上不需要同时是 IFnj.u.f.Predicate 等的类。

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

(let [orig [1 2 3]

  st (atom [])]

(.forEach orig (jfn 消费者 [x] (swap! st 累加 x)))
(= @st orig))
`

(链接:1) https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.8
(链接:2) 峰值 https://gist.github.com/ghadishayban/0ac41e81d4df02ff176c22d16ee8b972

0

评论者:jwhitlark

嗯,这将是一个改进。我在实践中遇到的实际问题是,我经常深入流畅的接口中,不一定知道确切的类。话虽如此,这通常只存在于几个地方。是否需要有一个注册表?也许像这样

(自动推理-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包)而不是常规的反射。我不确定是否已经在抽象类上测试过这个方法。

其用法与Ghad i发布的内容相似

`
(defsam my-sam
java.util.function.Predicate
[x]
(= x "匹配成功"))

(=> (Stream/of "不匹配" "匹配成功")

(.filter my-sam)
(.collect Collectors/toList)

(=> (IntStream/range 0 10)

(.filter (sam* java.util.function.IntPredicate odd?))
(.collect Collectors/toList)

`

它使用(MethodHandleProxies.asInterfaceInstance)来创建一个调用Clojure函数的方法句柄的代理实例。它不尝试验证参数数量,仅将其视为将args委托给IFn.applyTo(ISeq)。不确定这是否是最有效的方法,但对我个人的需求来说非常适用。

我认为(Java.lang.invoke.MethodHandles)可能是满足此类用例的首选方法。但我发现跟踪如何使用它比较困难,所以我没有深入研究。

我与我的方法(以及Ghad i的方法)的主要功能问题是必须显式提供要代理的接口。Java lambda和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个子数的Clojure函数。这个接口在现实中变得越来越常见,可能值得特别处理。(我们不应该、也不能对defrecords做类似的事情,因为它们已经有了与Supplier的get方法冲突的方法get。)

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