Clojure

The Reader

Clojure是一种谐音语言,是一个奇特的术语,用于描述Clojure程序由Clojure数据结构表示的事实. 这是Clojure(和Common Lisp)与大多数其他编程语言之间的非常重要的区别-Clojure是根据数据结构的评估而不是根据字符流/文件的语法定义的. Clojure程序可以操纵,转换和生成其他Clojure程序,这是非常普遍且容易的.

就是说,大多数Clojure程序都是从文本文件开始的,而读者的任务是解析文本并生成编译器将看到的数据结构. 这不仅是编译器的一个阶段. 阅读器和Clojure数据表示形式在许多可能使用XML或JSON等的相同上下文中都有自己的实用程序.

有人可能会说,阅读器的语法是用字符定义的,而Clojure语言的语法是用符号,列表,矢量,地图等定义的.阅读器由read的函数表示,该函数读取下一种形式(不是字符).从流中返回,并返回该表单表示的对象.

由于我们必须从某个地方开始,因此此参考从阅读者表格开始于评估开始. 这将不可避免地需要讨论其描述细节和编译器将随之解释的数据结构.

Reader forms

Symbols

  • 符号以非数字字符开头,并且可以包含字母数字字符以及*,+,!,-,_,',?,<,>和=(最终可以允许其他字符).

  • " /"具有特殊含义,可以在符号中间使用一次,以将名称空间与名称分开,例如my-namespace/foo . " /"本身命名除法函数.

  • '.' 具有特殊含义-可以在符号中间使用一次或多次以指定完全限定的类名,例如java.util.BitSet或命名空间名称. 以"."开头或结尾的符号. 由Clojure保留. 包含/或的符号. 据说是"合格"的.

  • Clojure保留以":"开头或结尾的符号. 一个符号可以包含一个或多个非重复的':'.

Literals

  • 字符串-包含在"双引号"中. 可能跨越多行. 支持标准Java转义字符.

  • 数字-通常按照Java表示

    • 整数可以无限长,当在range和clojure.lang.BigInts范围内时,将被读为Longs. 带N后缀的整数始终被视为BigInts. 如果可能,可以在2到36之间的任何基数中指定它们(请参见Long.parseLong() ); 例如2r1010108r5236r16 ,和42都是一样长.

    • 浮点数读为Doubles; 带有M后缀的它们将被读取为BigDecimals.

    • Ratios are supported, e.g. 22/7.

  • 字符-以反斜杠开头: \c . \newline\space\tab\formfeed\backspace\return产生相应的字符. Unicode字符用\uNNNN表示,就像Java中一样. 八进制用\oNNN表示.

  • nil表示"无/无值"-表示Java null并测试逻辑false

  • 布尔值- truefalse

  • 符号值- ##Inf##-Inf##NaN

  • 关键字-关键字类似于符号,但以下内容除外:

    • 它们可以并且必须以冒号开头,例如:fred.

    • 它们不能包含"." 在名称部分或名称类别中.

    • 像符号一样,它们可以包含名称空间:person/name ,其中可以包含'.'.

    • 以两个冒号开头的关键字在当前名称空间中自动解析为合格关键字:

      • 如果关键字不合格,则名称空间将是当前名称空间. 在user::rect读为:user/rect .

      • 如果关键字是合格的,则将使用当前名称空间中的别名来解析名称空间. 在x别名为example的命名空间中, ::x/foo解析为:example/foo .

Lists

列表是用括号括起来的零个或多个形式: (abc)

Vectors

向量是用方括号括起来的零个或多个形式: [1 2 3]

Maps

  • 映射是用括号括起来的零个或多个键/值对: {:a 1 :b 2}

  • 逗号被认为是空格,可用于组织成对的字符: {:a 1, :b 2}

  • 键和值可以是任何形式.

Map namespace syntax

在Clojure 1.9中添加

映射文字可以使用#:ns前缀(可选)为映射中的键指定默认的命名空间上下文,其中ns是命名空间的名称,并且前缀在映射的左括号{之前. 此外, #::可用于自动解析名称空间,其名称与自动解析的关键字具有相同的语义.

具有名称空间语法的地图文字与没有以下内容的地图具有以下区别:

  • Keys

    • 使用默认名称空间读取没有名称空间的关键字或符号键.

    • Keys that are keywords or symbols with a namespace are not affected except for the special namespace _, which is removed during read. This allows for the specification of keywords or symbols without namespaces as keys in a map literal with namespace syntax.

    • 不是符号或关键字的键不受影响.

  • Values

    • 值不受影响.

    • 嵌套的地图文字键不受影响.

例如,以下具有名称空间语法的映射文字:

#:person{:first "Han"
         :last "Solo"
         :ship #:ship{:name "Millennium Falcon"
                      :model "YT-1300f light freighter"}}

读取为:

{:person/first "Han"
 :person/last "Solo"
 :person/ship {:ship/name "Millennium Falcon"
               :ship/model "YT-1300f light freighter"}}

Sets

集是零个或多个用大括号括起来的形式,其后带有##{:a :b :c}

deftype, defrecord, and constructor calls (version 1.3 and later):

  • 可以使用Java类,deftype和defrecord构造函数的调用,使用它们的完全限定的类名,其后带有#和一个向量: #my.klass_or_type_or_record[:a :b :c]

  • 向量部分中的元素未经评估就传递给相关的构造函数. defrecord实例也可以使用类似的形式创建,即采用地图: #my.record{:a 1, :b 2}

  • 映射中的键值未评估地分配给defrecord中的相关字段. 在文字映射中没有相应条目的所有defrecord字段均被指定为nil作为其值. 映射文字中的所有其他键值都将添加到结果defrecord实例中.

Macro characters

读取器的行为由内置结构和称为读取表的扩展系统的组合驱动. 读取表中的条目提供从某些字符(称为宏字符)到特定读取行为(称为读取器宏)的映射. 除非另有说明,否则不能在用户符号中使用宏字符.

Quote (')

'form(quote form)

Character (\)

如上所述,产生一个字符文字. 示例字符文字为: \a \b \c .

以下特殊字符文字可用于常见字符: \newline\space\tab\formfeed\backspace\return .

Unicode支持遵循Java约定,并具有与基础Java版本相对应的支持. Unicode文字的格式为\uNNNN ,例如\u03A9是Ω的文字.

Comment (;)

单行注释使读者忽略从分号到行尾的所有内容.

Deref (@)

@form ⇒ (deref form)

Metadata (^)

元数据是与某些对象关联的映射:符号,列表,向量,集合,映射,返回IMeta的带标记文字以及记录,类型和构造函数调用. 元数据读取器宏首先读取元数据,并将其附加到下一个读取的表单(请参阅with-meta将meta附加到对象):
^{:a 1 :b 2} [1 2 3]产生具有元数据映射{:a 1 :b 2}的向量[1 2 3] .

简写版本允许元数据为简单的符号或字符串,在这种情况下,它被视为具有:tag键和(已解析的)符号或字符串的值的单个条目映射,例如:
^String x^{:tag java.lang.String} x

这样的标签可用于将类型信息传达给编译器.

另一种速记版本允许元数据作为关键字,在这种情况下,元数据被视为具有关键字关键字和值为true的单个条目映射,例如:
^:dynamic x^{:dynamic true} x

可以链接元数据,在这种情况下,它们可以从右到左合并.

Dispatch (#)

调度宏使读取器使用另一个表中的读取器宏,该宏由以下字符索引

  • #{}-请参阅上面的集

  • 正则表达式模式(#"模式")

    正则表达式模式在读取时进行读取和编译 . 结果对象的类型为java.util.regex.Pattern. 正则表达式字符串不遵循与字符串相同的转义字符规则. 具体来说,模式中的反斜杠将被视为自身(并且不需要使用其他反斜杠进行转义). 例如, (re-pattern "\\s*\\d+")可以更简洁地写为#"\s*\d+" .

  • 变量引号(#')

    #'x(var x)

  • 匿名函数文字(#())

    #(…​)(fn [args] (…​))
    其中args由形式为%,%n或%&的参数文字确定. %是%1的同义词,%n指定第n个arg(从1开始),%&指定其余的arg. 这不是fn的替代品-惯用的用法是非常短的一次性映射/过滤器fns等. #()表单不能嵌套.

  • 忽略下一个表格(#_)

    读者完全跳过了#_之后的形式. (这比产生nil的comment宏更完整).

Syntax-quote (`, note, the "backquote" character), Unquote (~) and Unquote-splicing (~@)

对于除符号,列表,向量,集合和映射以外的所有形式,`x与'x相同.

对于Symbols,syntax-quote会在当前上下文中解析符号,从而生成完全限定的符号(即,名称空间/名称或fully.qualified.Classname). 如果符号不是命名空间限定的,并且以'#'结尾,则将其解析为已生成的符号,该符号的名称已附加了'_'和唯一的ID. 例如x#将解析为x_123. 在语法引用的表达式中对该符号的所有引用都解析为相同的生成符号.

对于列表/向量/集合/地图,语法引号建立相应数据结构的模板. 在模板中,不合格的表单的行为就像递归语法引用一样,但是可以通过用unquote或unquote-splicing对它们进行限定来将表单从递归引用中排除,在这种情况下,它们将被视为表达式,并在模板中被替换为它们.值或值序列.

例如:

user=> (def x 5)
user=> (def lst '(a b c))
user=> `(fred x ~x lst ~@lst 7 8 :nine)
(user/fred user/x 5 user/lst a b c 7 8 :nine)

用户程序当前无法访问读取的表.

extensible data notation (edn)

Clojure的阅读器支持可扩展数据符号(edn)的超集. edn规范正在积极开发中,并通过以与语言无关的方式定义Clojure数据语法的子集来补充本文档.

Tagged Literals

标记文字是Clojure对edn 标记元素的实现.

当Clojure启动时,它将在类路径的根目录下搜索名为data_readers.clj文件. 每个此类文件必须包含一个符号的Clojure映射,如下所示:

{foo/bar my.project.foo/bar
 foo/baz my.project/baz}

每对中的密钥是Clojure读取器可以识别的标签. 该对中的值是Var的完全限定名称,阅读器将调用它来解析标记后的形式. 例如,鉴于上面的data_readers.clj文件,Clojure阅读器将解析此格式:

#foo/bar [1 2 3]

通过在向量[1 2 3]上调用Var #'my.project.foo/bar . 数据读取器函数在被读取器读取为常规Clojure数据结构之后,将以其形式调用.

没有名称空间限定符的阅读器标记保留给Clojure使用. 默认阅读器标签在default-data-readers中定义,但可以在data_readers.clj或通过重新绑定* data-readers *来覆盖. 如果没有为标签找到数据读取器,则将使用标签和值调用* default-data-reader-fn *中绑定的函数,以产生一个值. 如果* default-data-reader-fn *为nil(默认值),则会引发RuntimeException.

Built-in tagged literals

Clojure 1.4引入了即时UUID标记文字. #inst "yyyy-mm-ddThh:mm:ss.fff+hh:mm"的格式为#inst "yyyy-mm-ddThh:mm:ss.fff+hh:mm" . 注意:此格式的某些元素是可选的. 有关详细信息,请参见代码. 默认情况下,默认阅读器会将提供的字符串解析为java.util.Date . 例如:

(def instant #inst "2018-03-28T10:48:00.000")
(= java.util.Date (class instant))
;=> true

由于* data-readers *是可以绑定的动态var,因此您可以将默认阅读器替换为其他阅读器. 例如, clojure.instant/read-instant-calendar会将文字解析为java.util.Calendar ,而clojure.instant/read-instant-timestamp会将其解析为java.util.Timestamp

(binding [*data-readers* {'inst read-instant-calendar}]
  (= java.util.Calendar (class (read-string (pr-str instant)))))
;=> true

(binding [*data-readers* {'inst read-instant-timestamp}]
  (= java.util.Timestamp (class (read-string (pr-str instant)))))
;=> true

#uuid标记的文字将被解析为java.util.UUID

(= java.util.UUID (class (read-string "#uuid \"3b8a31ed-fd89-4f1b-a00f-42e3d60cf5ce\"")))
;=> true

Default data reader function

If no data reader is found when reading a tagged literal, the *default-data-reader-fn* is invoked. You can set your own default data reader function and the provided tagged-literal function can be used to build an object that can store an unhandled literal. The object returned by tagged-literal supports keyword lookup of the :tag and :form:

(set! *default-data-reader-fn* tagged-literal)

;; read #object as a generic TaggedLiteral object
(def x #object[clojure.lang.Namespace 0x23bff419 "user"])

[(:tag x) (:form x)]
;=> [object [clojure.lang.Namespace 599782425 "user"]]

Reader Conditionals

Clojure 1.7引入了可扩展文件的新扩展名(.cljc),该文件可以由多个Clojure平台加载. 管理特定于平台的代码的主要机制是将代码隔离到最小的名称空间集合中,然后提供这些名称空间的特定于平台的版本(.clj / .class或.cljs).

在无法隔离代码的各个部分的情况下,或者在大多数代码仅可移植为特定于平台的小部分的情况下,1.7还引入了阅读器条件 ,仅在cljc文件和默认的REPL中受支持. 读者有条件的条件应谨慎使用,并且仅在必要时使用.

读者条件是从#?开始的新读者分派形式#?#?@ . 两者都由一系列交替的特征和表达式组成,类似于cond . 每个Clojure平台都有一个众所周知的"平台功能"- :clj:cljs:cljr . 依次检查阅读器条件中的每个条件,直到找到与平台功能匹配的功能. 有条件的读者将读取并返回该功能的表达式. 每个未选择的分支上的表达式将被读取但被跳过. 众所周知的:default功能将始终匹配,可用于提供默认功能. 如果没有分支匹配,则不会读取任何形式(就像没有读取器条件表达式一样).

以下示例在Clojure中读为Double / NaN,在ClojureScript中读为js / NaN,在任何其他平台中读为nil:

#?(:clj     Double/NaN
   :cljs    js/NaN
   :default nil)

#?@的语法完全相同,但是期望表达式返回一个可以拼接到周围上下文中的集合,类似于语法引用中的非引用拆分. 不支持在顶层使用阅读器条件拼接,这将引发异常. 一个例子:

[1 2 #?@(:clj [3 4] :cljs [5 6])]
;; in clj =>        [1 2 3 4]
;; in cljs =>       [1 2 5 6]
;; anywhere else => [1 2]

readread-string函数可以选择将选项映射作为第一个参数. 可以使用以下键和值在选项图中设置当前功能集和读取器的条件行为:

  :read-cond - :allow to process reader conditionals, or
               :preserve to keep all branches
  :features - persistent set of feature keywords that are active

如何从Clojure测试ClojureScript阅读器条件的示例:

(read-string
  {:read-cond :allow
   :features #{:cljs}}
  "#?(:cljs :works! :default :boo)")
;; :works!

但是,请注意,Clojure阅读器还将始终注入平台功能:clj. 有关平台无关的阅读,请参见tools.reader .

如果使用{:read-cond :preserve}调用{:read-cond :preserve}器,则读取器的条件分支和未执行分支将作为数据保留为返回形式. 有条件的阅读器将作为一种类型返回,该类型支持使用:form:splicing?检索关键字的关键字:splicing? 旗. 读取但跳过的带标记的文字将作为支持使用:form:tag键的键检索关键字的类型返回.

(read-string
  {:read-cond :preserve}
  "[1 2 #?@(:clj [3 4] :cljs [5 6])]")
;; [1 2 #?@(:clj [3 4] :cljs [5 6])]

以下函数也可以用作这些类型的谓词或构造函数:
读者有条件吗? 读者条件 标记文字? 标签文字

by  ICOPY.SITE