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 一样工作,我们只是在主体中返回它。

但是,我们想要定义一个可变参数数量的函数,所以我们需要 %& 来指示参数。然而,参数对于结果来说是无关紧要的......参数的唯一目的是与匿名函数简写沟通参数的数量或参数数量。

虽然有些不自然,但这是一种方法。

(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)
       (filter
        #(and (symbol? %)
              (or (anons (name %))
                  (re-find multi (name %)))
              %))))

(defn derive-function [form]
  (let [args       (infer-args form)
        args->syms (zipmap args (repeatedly #(gensym "arg")))
        new-args   (vals args->syms)]
  `(fn [~@new-args] ~(w/postwalk-replace args->syms form))))


因此,读取器提供了一个由-the-form定义的Clojure形式,例如`(+ 1 (* % 3))`。这个形式被推断为`fn`调用体的“主体”,其参数由任何前缀为`%`的符号的存在暗示。因此,我们传递主体形式到一个函数中,该函数提取参数,然后以推断出的参数和原始主体来发射一个`fn`形式,其中将原始的参数替换为目的生成的符号。由于dispach函数与`(`相关联,我们总是必须传递一个可读的sexpr作为函数体。因此,没有直接使用现有语法来传递返回简单值的函数的方法。
+1
by

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

我可以这样做

(constantly 1)

但使用`#()`

#(do %& 1)

%&是必须的,以确保它能对任何数量都正常工作。但我们想要忽略参数。do返回最后一个表达式的值,所以在这种情况下,它会返回`1`。

...