2024 Clojure 状态调查中分享你的看法!

欢迎!请访问关于页面获取更多关于此功能的信息。

0
语法和读取器

我开始学习 Clojure。教程要求定义 constantly 函数。我使用 fn 函数管理它。如何使用 #() 来实现?

2 个答案

0

编辑
 
最佳答案

[编辑,因为回答中缺少#]

由于 constantly 的意图是允许任意参数的函数始终返回一个常数,所以这种做法有点笨拙。#(...) 匿名函数缩写提供了快速定义匿名函数的简便方法,这些函数根据不同的参数数量工作。

0 - #(vector)
1 - #(vector %1)
2 - #(vector %1 %2)
...
n - #(vector %1 %2 ... %n)
& args - #(apply vector %&)

不过,有一些限制,因为你在定义函数体……如果你试图定义类似 identity 的东西,它最终会看起来像一个调用。

#(%) ;;这会尝试将单个参数作为一元函数调用。

一种解决方案是使用 let 或其他形式绑定输入,以传达参数数量或参数的元数……

#(let [v %] v)

#(do %)

将正常工作,就像 identity 一样,我们只是在体中返回它。

但是,我们希望定义一个可变参数的函数,所以我们想要 %& 表示参数。然而,参数对于结果来说是无意义的……唯一的含义是向匿名函数糖传达参数的数量或参数的元数。

虽然有些awkward,但这是其中一种方法。

(defn constantly2 [v]
;;ignore the arguments, but communicate a varargs function
   #(let [_ %&]  
          v))
user=> (def f (constantly2 10))
#'user/f
user=> (f 1 2 3 4)
10
user=> (f 1)
10
user=> (f 5)
10
user=> (f :a)
10

我可能不会使用这种 #(..) 语法糖来处理这类事情。这种简写对于非常简单的内联匿名函数最有用,即便如此,我还是通常更倾向于明确写出 fn

谢谢。我想可能确实有些尴尬。我不明白为什么 #(%) 被视为函数调用。读者是先看到 (%) 并尝试对其进行评估,然后再解析 # 吗?
我确定 [FnReader](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/LispReader.java#L861) 被映射到 \( 字符的派发读取器,它直接读取表示式并构建一个要评估的 `fn` 形式。
  
    user=> (read-string "#(+ % 2)")
    (fn* [p1__3#] (+ p1__3# 2))
    user=> (read-string "#(%)")
    (fn* [p1__6#] (p1__6#))
感谢提供 read-string 函数。我一直不知道这个函数。看起来像是 #() 扩展到的 fn 模板有额外的 (),这是要求 #() 中的形式可以被评估的。
简而言之,这是我看到的情况(以下将其转换为clojure,省略了一些验证内容)


(require '[clojure.walk :as w])
(require '[clojure.string :as s])

(def the-form '(+ 1 (* % 3)))

(def anons #{"%" "%&"})
(def multi #"%[0-9]*")
(defn infer-args [form]
  (->> form
       (tree-seq coll? seq)
       (筛选
        #(且 (% 是符号?)
                (如果是匿名 (%))
                  或 (正则查找多 (%)))
              %))))

(定义函数 derive-function [form]
  (let [args       (推断参数形式)
        args→syms (zipmap 参数 (重复 #(生成独特符号 "arg") 大小为 参数 数量))
        new-args   (参数→syms 的值]
    `(fn [~@new-args] ~(w/postwalk-replace 参数→syms 形式)))


因此,读取器提供了 clojure 形式,`(+ 1 (* % 3))`,由 the-form 定义。该形式被推断为 `fn` 调用的 "体",其中参数由任何前面带有 % 的符号的存在隐含。因此,我们将体形式传递给一个函数,该函数刮取并列出参数,然后输出一个 `fn` 形式,其中包含推断的参数和原始体,其中原始参数已被 gensym 替换。由于派发函数与 `(` 关联,我们必须始终传递一个可读的 sexpr 作为函数体。所以,无法以现有语法直接传递返回简单值的函数。
+1 投票

要使用 #() 简写定义 constantly,我会这样做。假设我想创建一个总是返回 1 的函数。

我可以这样做

(常数 1)

但要使用 #()

#(do %& 1)

%& 是必需的,以确保它适用于任何参数数量。但我们想忽略参数。do 返回最后一个表达式的值,所以在这个情况下,它将返回 1

...