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

我们想要定义一个带可变参数的函数,因此我们想用 %& 来表示参数。然而,对于结果来说,这些参数毫无意义……它们唯一的用途就是向匿名函数糖(sugar)传达参数的数量或参数的个数。

虽然有点不自然,但这确实是一种可行的方法。

(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]*")
(定义 infer-args [形式]
  (->> 形式
       (树-序列 coll? 序列)
       (筛选
        #(和 (符号? %)
              (或 (匿名 (名称 %))
                  (正则-查找 multi (名称 %))))
              %)

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


因此读者提供了一个 Clojure 形式,`(+ 1 (* % 3))`,这是由 the-form 定义的。此形式被推断为 `fn` 调用的 "主体",其中的参数由存在任何带 % 前缀的符号来暗示。因此,我们将主体形式传递给一个函数,该函数将其刮取以确定参数,然后发出一个具有推断参数和原始主体的 `fn` 形式,其中原始参数已被 gensym 替换。由于分发函数与 `(` 相关联,我们总是必须传递一个可读的 sexpr 作为函数主体。因此,没有直接使用既定语法传递返回简单值的函数的方法。
+1 投票

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

我可以这样做

(constantly 1)

但要用 #()

#(do %& 1)

%& 是必需的,以确保为其提供任意数量参数都能正常工作。但我们想忽略参数。`do` 返回最后一个表达式的值,所以在这种情况下,它将返回 1

...