【问题标题】:Clojure : loading dependencies at the REPLClojure:在 REPL 中加载依赖项
【发布时间】:2012-04-06 08:08:17
【问题描述】:

我最近了解到(感谢技术),在 REPL ---

这失败了:

user=> (:require [clojure.set :as set])
java.lang.ClassNotFoundException: clojure.set (NO_SOURCE_FILE:24)

而这成功了:

user=> (require '[clojure.set :as cs]) 
nil

在加载 clojure.set 类时。

上下文:前一行是从命名空间源文件中复制的。

我的主要问题是:我们做了什么改变,通过交换 : 和 ' 字符,现在允许后一个命令成功?

我的第二个问题是,总的来说 - 与在普通 clojure 源文件中做事相比,在 REPL 做事的指导方针是什么?假设我们可以加载我们的 repl来自 LEININGEN 项目的根目录,因此至少这些 jar 文件将在磁盘上的依赖项子目录中可用。

【问题讨论】:

  • 我想这确实是在问“为什么 require 是普通 clj 文件中的关键字 --- 但 REPL 中的标准函数”。
  • 我认为第二部分有点笼统,应该删除或拆分成一个完全独立的帖子。

标签: clojure read-eval-print-loop


【解决方案1】:

我会从高层次到你的具体问题:

Clojure(或 LISP)的一般工作原理

REPL 或 Read-Eval-Print 循环是 LISP 设计的核心:

  • 阅读器将字符流转换为数据结构(称为阅读器表单)。
  • 评估者收集读者表格并对其进行评估。
  • 打印机发出评估器的结果。

因此,当您在 REPL 中输入文本时,它会通过每个步骤来处理您的输入并将输出返回到您的终端。

阅读器表单

首先是clojure 阅读器表单。这将非常简短,我鼓励您read 或观看(part 1part 2)。

clojure 中的 symbol 是可以表示特定值(如变量)的形式。符号本身可以作为数据传递。它们类似于 c 中的指针,只是没有内存管理。

前面带有冒号的符号是关键字。关键字就像符号一样,除了关键字的值始终是它们本身 - 类似于字符串或数字。它们与 Ruby 的符号相同(也以冒号为前缀)。

表单前的引号告诉评估者保持数据结构不变:

user=> (list 1 2)
(1 2)
user=> '(1 2)
(1 2)
user=> (= (list 1 2) '(1 2))
true

虽然引用不仅仅适用于列表,但它主要用于列表,因为 clojure 的求值器通常会将列表作为类似函数的调用来执行。使用 ' 是引用宏的简写:

user=> (quote (1 2)) ; same as '(1 2)
(1 2)

引用基本上指定要返回的数据结构,而不是要执行的实际代码。所以你可以引用引用符号的符号。

user=> 'foo ; not defined earlier
foo

并且引用是递归的。所以里面的所有数据也都被引用了:

user=> '(foo bar)
(foo bar)

要在不引用的情况下获得(foo bar) 的行为,您可以对其进行评估:

user=> (eval '(foo bar)) ; Remember, foo and bar weren't defined yet.
CompilerException java.lang.RuntimeException: Unable to resolve symbol: foo in this context, compiling:(NO_SOURCE_PATH:1)
user=> (def foo identity)
#'user/foo
user=> (def bar 1)
#'user/bar
user=> (eval '(foo bar))
1

还有很多要引用的内容,但这超出了这个范围。

需要

至于 require 语句,我假设您以以下形式找到前者:

(ns my.namespace
    (:require [clojure.set :as set]))

ns 是一个 macro,它将 :require 表达式转换为您描述的后一种形式:

(require '[clojure.set :as set])

还有一些命名空间工作。在 REPL 中询问 ns 的文档时描述了基础知识。

user=> (doc ns)
-------------------------
clojure.core/ns
([name docstring? attr-map? references*])
Macro
  Sets *ns* to the namespace named by name (unevaluated), creating it
  if needed.  references can be zero or more of: (:refer-clojure ...)
  (:require ...) (:use ...) (:import ...) (:load ...) (:gen-class)
  with the syntax of refer-clojure/require/use/import/load/gen-class
  respectively, except the arguments are unevaluated and need not be
  quoted. (:gen-class ...), when supplied, defaults to :name
  corresponding to the ns name, :main true, :impl-ns same as ns, and
  :init-impl-ns true. All options of gen-class are
  supported. The :gen-class directive is ignored when not
  compiling. If :gen-class is not supplied, when compiled only an
  nsname__init.class will be generated. If :refer-clojure is not used, a
  default (refer 'clojure) is used.  Use of ns is preferred to
  individual calls to in-ns/require/use/import:

REPL 用法

一般情况下,不要在 REPL 中使用 ns,而只需使用 requireuse 函数即可。但是在文件中,使用ns 宏来做这些事情。

【讨论】:

    【解决方案2】:

    区别在于require是用于导入代码的函数,而:require是关键字。

    记住将关键字用作函数时会发生什么:

    => (type :require)
    clojure.lang.Keyword
    => (:require {:abc 1 :require 14})
    14
    

    它在地图中查找自己。因此,当您将 [clojure.set :as set] 传递给关键字时,它会尝试将其评估为向量,但由于它不知道 clojure.set 是什么而失败。 Clojure docs 说:

    关键字为一个参数(映射)的调用()实现 IFn 可选的第二个参数(默认值)。例如 (:mykey my-hash-map :none) 的含义与 (get my-hash-map :mykey :none) 相同。

    你可能被the ns macro弄糊涂了:

    (ns foo.bar
      (:refer-clojure :exclude [ancestors printf])
      (:require (clojure.contrib sql sql.tests))    ;; here's :require!
      (:use (my.lib this that))
      (:import (java.util Date Timer Random)
               (java.sql Connection Statement)))
    

    【讨论】:

    • 是的,OP 似乎研究了 :require 的用法并创建了与 (require) 的心理等效性,这是可以理解的;它可能来自查看使用前者的代码;不同之处当然是形式是 (ns ...) vs (require ...)
    【解决方案3】:

    ns

    当您键入时:

    (ns some-great-ns 
      :require my-form) 
    

    您使用:require reference,在其中说明您想从给定的命名空间中使用什么。相当于写:

    (in-ns 'some-great-ns)
    (require 'my-form)
    

    请注意,在ns 形式中(与in-ns 函数调用不同),您不必用' 引用您的符号。您永远不必在 ns 中引用符号。

    require函数

    如前所述,可以在某个给定的命名空间中运行:(require 'some-great-ns),以便您可以使用它。要使用它,您必须使用完整的限定名称,除非您在需要命名空间之后还使用:refer 函数:(refer 'some-great-ns)

    您可以将这两个功能合二为一:(use 'some-great-ns)。现在你不需要写:(some-great-ns/my-form)。简单地说:my-form

    当然,您也可以在宏引用和函数中使用:as:exclude:only:rename 关键字。

    宏和函数的区别:

    1. 如上所述,函数中符号的使用,宏中不需要
    2. 您可以在 (:require) 引用中要求多个库,如下所示:
    (ns my-great-namespace.core
      (:require [some-other-ns.a.b :as ab]
                [some-other-other-ns.c.d :as cd]))
    

    function 写作中你应该写两行:

    (in-ns my-great-namespace.core)
    (require 'some-other-ns.a.b :as 'ab)
    (require 'some-other-other=ns.c.d :as 'cd)
    
    1. require 引用还允许您引用名称,例如:
    (ns my-great-namespace.core
      (:require [some-other-ns.a.b :refer [some-func]]))
    

    你应该在哪里做:

    (in-ns my-great-namespace.core)
    (require 'some-other-ns.a.b)
    (refer 'some-other-ns.a.b :only ['some-func])
    
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多