2024 Clojure状态调查中分享您的想法!

欢迎!请查看关于页面以获取更多关于此的工作信息。

+57
Java互操作
已关闭

将基线移至Java 8允许我们考虑内置于critical 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.*的地方使用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
by

有人编写了一个补丁,以最佳方式解决这个问题,简单来说,它就是这样做的,与 Java 为 lambda 做的完全一样,但它为 Clojure FN 做了同样的工作。

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

by
糟糕,我们最好为 CLJ-2637 分配一个“问题”,以免这里的评论在这两种方法之间变得一团糟。
+1 投票
by

刚发现这个,我想提供一些用法/上下文信息。

在与使用 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。为了以最有效的方式使用并发哈希表,需要使用compute、computeIfPresent、computeIfAbsent - (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#compute-K-java.util.function.BiFunction-)。
+1 投票
by

数据请求

除了 java.util.stream.Stream 和现有 JDK API(如 new Thread(() -> doSomething(x)))的重构之外,还有哪些lambda/SAM库在使用Clojure时觉得不方便?

0
by
_评论由:jwhitlark_ 发布:


;;我从实验kafka streams的一些草稿代码中挖出了这个代码。

;;我将再找一个例子来展示使用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<K,V,R>
;; apply(K key, V value)
(defmacro kv-mapper [kv & body]
  `(reify KeyValueMapper
     (apply [_# ~(first kv) ~(second kv)]
       ~@body)))

;; public interface ValueMapper<V1,V2>
;; 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映射
                    (使用(reify ValueMapper
                            (apply [_ v])
                            (println v)
                            (str v)
             转换为流(.toStream)
             输出到文件(.to "wordcount-output")
         [构建器 wc])
0

评论由:gshayban 撰写

JLS通过寻找匹配的函数式接口(即“单个抽象方法”类)来推断lambda类型(链接:1)(它们是接口还是抽象类)。我们可以有一个类似于reify的帮助器来检测这些类(链接:2)。您需要提示目标类。我们并不需要既是IFn又是j.u.f.Predicate等的类。

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

(let [orig [1 2 3])

  st (atom [])]

(原始.forEach (jfn Consumer [x] (swap! st conj x))))
(= @st 原始))
`

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

以下是在我(链接:[github](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)

`

它使用(链接:[MethodHandleProxies.asInterfaceInstance](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/MethodHandleProxies.html#asInterfaceInstance-java.lang.Class-java.lang.invoke.MethodHandle-))来创建一个接口代理实例,该实例通过调用Clojure函数调用方法句柄。它不尝试验证参数数量,只是将其视为可变参数,委托给IFn.applyTo(ISeq)。不确定它是否是最有效的,但对我的需求来说是有效的。

我认为(链接:[LambdaMetaFactory](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/invoke/MethodHandles.html))可能是满足这类用例的理想方式。但对我来说,使用它的方法更难以确切地了解,所以我最终并没有深入探讨。

我对我的方法(和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个参数。这个接口在实践中变得越来越常见;这可能是有价值的,可以为其特设。

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