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

欢迎!请查看关于页面,了解更多关于如何使用这个功能的信息。

0 投票
语法和读取器

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

2 条回答

0 投票

编辑
 
最佳答案

[由于缺少#,编辑答案]

走这条路有点蹩脚,因为constantly的意图是允许任意参数的函数,它们总是返回一个常量。#(...)匿名函数简写提供了快速定义匿名函数的便捷方式,这些函数以不同的arity(参数数量)工作。

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

但是有一些限制,因为你在定义函数体...如果你尝试定义一个像identity这样的东西,它最终会看起来像是一个调用。

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

可以通过在let或其他形式中绑定输入来工作,从而表示arity(参数数量)...

#(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

by
谢谢。我觉得这会很别扭。我不明白为什么#(%)会被视为函数计算。读者是否会在解析#之前先看到 (%) 并尝试去计算它?
by
我相信这是[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#))
by
谢谢你提供的read-string函数。之前不知道有这个。看起来#()扩展到fn模板中时多出了额外的(),这需要#()中的形式可以被评估。
by
简而言之,这是我看到的处理过程(转换为clojure,省略了一些验证内容)


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

(定义表单形式 '(+ 1 (* % 3)))

(定义匿名函数 #{"%" "%&"})
(定义多字符字符串 #"%[0-9]*")
(定义 infer-args 函数 [form])
  (->> form
       ((树的序数 coll? seq))
       ((过滤器
        #(并且 (符号? %)
              (匿名函数 (名称 %))
                  (正则表达式匹配 multi (名称 %)))
              %))))

(定义 derive-function 函数 [form])
  (let [参数       ((infer-args 形式))
        参数-到-符号 (zipmap 参数 ((重复 #(生成符号 "arg"))))
        new-参数   (参数-到-符号的值)
    `(fn [~@new-参数] ~(w/postwalk-replace 参数-到-符号 形式))))


所以读取器提供了一个由 def-form 定义的 clojure 形式 `(+ 1 (* % 3))`。这个形式被认为是 fn 调用的“主体”,参数通过任何前面带有 % 的符号的存在来暗示。所以我们传递主体形式到一个函数中,这个函数会刮取它以确定参数,然后输出一个带有推断参数的 fn 形式和原始的主体形式,其中原始参数被 gensym 中的一个替代。由于调度函数与 `(` 关联,我们必须始终传递一个可读的 sexpr 作为函数的主体。因此,没有直接使用现有语法传递返回简单值的函数的方法。
+1

要使用 `#()` 省略符定义 `constantly`,我会这样做。假设我想创建一个始终返回 `1` 的函数。

我可以这样做

(constantly 1)

但要使用 `#()` 做这个

#(do %& 1)

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

...