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 #{"%" "%&"})
(定义 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))))


因此,读者提供了一个 Clojure 表达式,`(+ 1 (* % 3))`,如 the-form 所定义的。此表达式被推断为 `fn` 调用的“主体”,其中参数是通过任何前缀为 % 的符号的存在的隐含的。因此,我们将主体表单传递给一个函数,该函数刮擦以确定参数,然后发出一个带有推断参数和原始主体的 `fn` 表达式,其中原始参数已被 gensym 替换。由于调度函数与 `(` 相关联,我们始终必须传递一个可读的 sexpr 作为函数体。因此,没有直接使用既定语法传递返回简单值的函数的方法。
+1

要使用 #() 省略号来定义 constantly,我会这样做。假设我想编写一个始终返回 1 的函数。

我可以这样做

(constantly 1)

但要使用 #()

#(do %& 1)

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

...