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类型的地方使用Lambdas,我们可以这样编写

    (-> 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.*的地方 just 能使用IFn的utt,那就太棒了!

+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。为了最有效地使用并发哈希表,您需要使用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的retrofit(如new Thread(() -> doSomething(x))),还有哪些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)))

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

函数定义 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"))]
    [builder wc]))
0投票

评论者:gshayban

Java语言规范(JLS)通过查找匹配的函数接口(也称为 "单抽象方法" 类)来推断lambda类型(链接:1)。我们可以有一个类似reify的帮助器来检测这些类(链接:2)。您需要指出目标类。我们实际上不需要既是Ifn又是j.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) 链接 https://gist.github.com/ghadishayban/0ac41e81d4df02ff176c22d16ee8b972

0投票

评论者:jwhitlark

嗯,这将是一个改进。我在实践中遇到的实际问题是,我经常深入到 Fluent 接口,并不一定知道确切的类。话虽如此,这通常只在少数地方发生。有一个注册表有意义吗?可能类似于

(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 函数。它不尝试验证参数数量,只是将其视为可变参数,委托给 ISeq.applyTo。不确定这是否是最高效的,但这对我来说是有效的。

我认为(链接: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 提出)
...