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

尽管如此,我们想要定义一个变量参数的函数,所以想用%&来指明参数。但参数对于结果来说没有意义...它们唯一的作用是将参数的数量或arity传递给匿名函数的简化语法。

虽然有些笨拙,但这是实现它的一种方法

(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`形式,该形式包含推断出的参数和原始体,其中原始参数已被gensym替换。由于分配函数与`(`相关联,我们始终必须传递一个可读的sexpr作为函数体。因此,无法直接使用既定语法传递返回简单值的函数。
+1

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

我可以这样做

(constantly 1)

但要使用#()

#(do %& 1)

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

...