【clojure • 一】符号使用汇总

一、 概述

  • Clojure中大量使用各种符号,初学时经常容易看晕,因此将学习过程中接触到的符号总结到一起,便于理解;
  • Clojure中的符号本质上都是用于简化代码的语法糖,编译时会用Reader进行解析;Clojure官方文档专门有一章用于介绍Reader,里面大部分都是关于符号的说明,详见 The Reader
  • Clojure官方的CheatSheet中也有对符号的总结,非常便于检索,详见Cheatsheet

二、数据结构相关符号

  • Clojure使用了大量符号来区分不同的数据类型,且引入了 解构 概念来拆分数据结构,导致符号之中还会嵌套更多的符号;

  • 如需了解常见数据类型及使用方法,建议参阅 Clojure数据类型

  • 如需了解集合及常用方法,建议参阅 Clojure集合

  • 如需了解解构方法,详见官方说明 Destructuring

    1. 数据定义

    • 【String】"Hello"
    • 【Char】\a
    • 【Keyword】:key
    • 【List】’(1 2 3)
    • 【Vector】[1 2 3]
    • 【Map】{key word key word}
    • 【Set】{1 2 3}
    • 【BigInt】100N
    • 【BigDecimal】0.01M
    • 使用 class obj 可以输出数据类型,使用 map?list? 等函数可以判断类型对不对

    2. 数据解构

    (let [[x y] [1 2 3]] ;x=1 y=2
    (let [[x y z] [1 2]] ;x=1 y=2 z=nil
    (let [[item1 _ item3 & remaining :as all] [1 2 3 4 5]]) ;item1=1, item3=3, remaining=[4 5] all=[1 2 3 4 5]
    (let [[a b :as A] [c d :as B]] [[1 2] [3 4]]) ;a=1 b=2 c=3 d=4 A=[1 2] B=[3 4]
    (let [{A :a B :b D :d :or {D "None"} :as all} {:a 1 :b 2 :c 3}]) ; A=1 B=2 D="None" all={:a 1 :b 2 :c 3}
    

二、命名规范及元数据

  • 在Clojure中定义一个函数或变量/符号时,除了采用合适的单词解释该函数/变量/符号以外,还会在命名中加入一些符号或元数据来包含更多信息;

  • 请注意下述规范和元数据不是强制性的,也不会影响到数据计算,但多了解一些有利于理解源码;

    1. 函数命名规范

    • 【?】有判断功能的函数以 ? 结束,如 fn? number?
    • 【*】有时会在一个命名空间中定义两个同名函数,其中一个函数用于对外提供接口并调用第二函数去实际执行(有时也把第二个函数放在第一个函数内部),此时实际运行的函数以 * 结尾

      如 metabase中,runf 函数用于从 context 中解析出一个函数并运行,这个被运行的内部函数可命名为 runf*


    2. 参数命名规范

    • Clojure中定义函数入参时不必指定数据类型,但可以用参数名来解释数据类型,例如
      -【 a 】Java数组
      -【 agt 】代办
      -【 coll 】容器
      -【 expr 】表达式
      -【 f 】函数
      -【 idx 】索引
      -【 r 】引用
      -【 v 】向量
      -【 val 】值

    3. 变量/符号命名规范

    • 会改变的参数以 * 开头和结尾,如 *data*
      def ^:dynamic *data*  ;定义一个变量,一般会在下文中用 binding 改变值
      

    4. 元数据使用

    • 元数据是对数据的附加说明,本身不参与任何计算,除非主动提取出来使用 参考说明
    (defn function ;函数添加元数据,查询指令(meta #'function)
    	"this is a note"
    	{:added "1.2"} ;元数据
    	[coll](expr))
    
    (def name ;变量添加元数据,查询指令 (meta name)
    	(with-meta
    		(expr)
    		{:creator "tim"}))
    

三、常用符号

  • 部分常用符号在不同语境下可以表达不同的意思,部分示例包括:

    1. Dot (.)

    • 主要用于使用Java标准库,注意Clojure只会自动引入Java.lang,其它库都要手动引入
    (Date.) ;类名字后加个”."用来创建一个对象
    (. (Date.) getTime) ;创建一个实例并调用示例方法
    (.getTime (Date.)) ;同上
    
    (def rnd (new java.util.Random)) ;同样可以创建一个Java实例
    (. rnd nextInt 10) ;调用实例方法
    
    (.replace (.toUpperCase "a b c d") "A" "X") ;调用字符串方法
    (System/currentTimeMillis) ;调用Java静态方法时使用 /
    

    2. Quote (’)

    (= 'form (quote form)) ;用于防止表达式被解析
    (= '(1 2 3) (list 1 2 3)) ;同上
    (eval '(+ 1 1)) ;返过来,解析表达式
    (eval (read-string "(println 1)")) ;解析字符串表达式
    
    (+' 1 2) ;用于支持任意精度,不会出现 integer overflow 报错,同样适用于 -' *'
    (+ 1N 2N) ;与上述效果一样,突破精度限制
    

    3. Character (\)

    • 用于表示字节 \a,特殊字符 \newline , 或Unicode \u03A9

    4. Unquote (~)

    5. More (&)

    • 用于函数的入参中,会将后续参数组装为一个list,list中数据类型可以不一致
    (defn date [person & stores]) ;stores将所有后续参数组装到一个list中
    

    6. Dispatch (#)

    • 调用宏来读取其参数,有多种用途
    #{} ;定义一个Set
    #"\s*\d+" ;定义一个正则表达式
    #'x ;等效于(var x),可以用 (meta #'x) 读取元数据
    #(println %) ;匿名函数
    

四、常用关键字

  • Clojure中可以通过使用预先定义的 :key 来实现一些方法或功能

    1. 断言 :pre & :pos

    • :pre 在函数调用之前检查参数是否符合条件,:post 在函数调用之后检查返回值是否符合条件;
    • 断言一般写在 [参数](代码) 之间,断言仅在开发环境起作用,正式环境会忽略,详见文章 :pre & :post

    2.私有化 ^:private

    • 可以标注在函数或变量上将其私有化,被 ^:private 标注后需要通过 #’ 访问
    • defn-defn ^:private 的效果是一样的,但后者应用更灵活更广泛
    ;; 将一个变量私有化,需要使用 #' 才能读取该变量
    (def ^:private a {})
    (meta a) ;=> nil
    (meta #'a) ;=> {:private true}
    
    ;; 注意,下述写法是 {} 私有化了,不能起到预期的效果
    (def a ^:private {})
    (meta a) ;=> {:private true}
    (meta #'a) ;=> nil
    
    ;; 灵活运用 private 定义一个私有的def-
    (defmacro def- [name & decls]
    	(list* `def (with-meta name (assoc (meta name) :private true)) decls))
    

    3. 变量 ^:dynamic

    • 定义一个可以被改变的变量,一般名称要用 *name* 的格式,且下文会用 binding 改变值;
    • 本质上这还是常量,只是在binding的作用域中会被临时绑定到一个新的值上,且仅对当前线程有效

    4. 常量 ^:const

    • 定义一个常量,加上这个注释之后,JVM会在编译时直接用值替换符号,否则JVM不会进行编译优化,而是在运行该函数时通过寻址去加载符号绑定的值,参考解答 stackoverflow
    ;; 下述两种表达方式等价,编译后代码一致
    (def ^:const pi 3.14)
    (defn circ2 [r] (* 2 pi r))
    
    (defn circ2 [r] (* 2 3.14 r))
    
    ;; 下述表达方式运行会慢一些,因为运行时会查找pi的值
    (def pi 3.14)
    (defn circ2 [r] (* 2 pi r))
    

    5. 数据类型说明 ^String

    • 在定义函数/入参时将数据类型写入元数据的tag键中,等效于 ^{:tag String}
    • 元数据仅仅记录说明,不会在运行中真正检查数据类型,即输入其它类型数据也不会有影响
    (defn ^String testB [^Number s] (println s)) ;可以添加到函数或入参上
    (defn ^String test) ;meta记录为 :tag java.lang.String
    (defn ^:String test) ;meta记录为 :String true
    

    6. 提高性能 ^:static

    • 可以提高运行性能,原理待补充
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值