前言

 作为一门函数式编程语言,深入了解函数的定义和使用自然是十分重要的事情,下面我们一起来学习吧!

3种基础定义方法

defn

定义语法

(defn name [params*]exprs*)

示例

(defn tap [ns x](println ns x)x)

fn

定义语法

(fn name? [params*]exprs*)

示例

(def tap(fn [ns x](println ns x)x))

其实defn是个macro,最终会展开为fn这种定义方式。因此后面的均以fn这种形式作说明。

Lambda表达式

定义语法

#(expr)

示例

(def tap#(do(println %1 %2)%2))

注意:

  1. Lambda表达式的函数体只允许使用一个表达式,因此要通过special formdo来运行多个表达式;
  2. 入参symbol为%1,%2,...%n,当有且只有一个入参时可以使用%来指向该入参。

Metadata——为函数附加元数据

 Symbol和集合均支持附加metadata,以便向编译器提供额外信息(如类型提示等),而我们也可以通过metadata来标记源码、访问策略等信息。
 对于命名函数我们自然要赋予它Symbol,自然就可以附加元数据了。
 其中附加:privatedefn-定义函数目的是一样的,就是将函数的访问控制设置为private(默认为public),但可惜的是cljs现在还不支持:private,所以还是要用名称来区分访问控制策略。
示例:

;; 定义
(defn^{:doc "my sum function":test (fn [](assert (= 12 (mysum 10 1 1)))):custom/metadata "have nice time!"}mysum [& xs](apply + xs));; 获取Var的metadata
(meta #'mysum)
;;=>
;; {:name mysum
;;  :custom/metadata "have nice time!"
;;  :doc "my sum function"
;;  :arglists ([& xs])
;;  :file "test"
;;  :line 126
;;  :ns #<Namespace user>
;;  :test #<user$fn_289 user$fn_289@20f443>}

若只打算设置document string而已,那么可以简写为

(defn mysum"my sum function"[& xs](apply + xs))

虽然cljs只支持:doc

根据入参数目实现函数重载(Multi-arity Functions)

示例

(fn tap([ns](tap ns nil))([ns x](println ns x))([ns x & more](println ns x more)))

参数解构

 cljs为我们提供强大无比的入参解构能力,也就是通过声明方式萃取入参

基于位置的解构(Positional Destructuring)

;; 定义1
(def currency-of(fn [[amount currency]](println amount currency)amount));; 使用1
(currency-of [12 "US"]);; 定义2
(def currency-of(fn [[amount currency [region ratio]]](println amount currency region ratio)amount));; 使用2
(currency-of [12 "US" ["CHINA" 6.7]])

键值对的解构(Map Destructuring)

;; 定义1,键类型为Keyword
(def currency-of(fn [{currency :curr}](println currency)));; 使用1
(currency-of {:curr "US"});; 定义2,键类型为String
(def currency-of(fn [{currency "curr"}](println currency)));; 使用2
(currency-of {"curr" "US"});; 定义3,键类型为Symbol
(def currency-of(fn [{currency 'curr}](println currency)));; 使用3
(currency-of {'curr "US"});; 定义4,一次指定多个键
(def currency-of(fn [{:keys [currency amount]}](println currency amount)));; 使用4
(currency-of {:currency "US", :amount 12});; 定义5,一次指定多个键
(def currency-of(fn [{:strs [currency amount]}](println currency amount)));; 使用5
(currency-of {"currency" "US", "amount" 12});; 定义6,一次指定多个键
(def currency-of(fn [{:syms [currency amount]}](println currency amount)));; 使用6
(currency-of {'currency "US", 'amount 12});; 定义7,默认值
(def currency-of(fn [{:keys [currency amount] :or {currency "CHINA"}}](println currency amount)));; 使用7
(currency-of {:amount 100}) ;;=> 100CHINA;; 定义8,命名键值对
(def currency-of(fn [{:keys [currency amount] :as orig}](println (:currency orig))))(currency-of {'currency "US", 'amount 12}) ;;=> US

可变入参(Variadic Functions)

通过&定义可变入参,可变入参仅能作为最后一个入参来使用

(def tap(fn [ns & more](println ns (first more))))(tap "user.core" "1" "2" "3") ;;=> user.core1

命名入参(Named Parameters/Extra Arguments)

 通过组合可变入参和参数解构,我们可以得到命名入参

(def tap(fn [& {:keys [ns msg] :or {msg "/nothing"}}](println ns msg)))(tap :ns "user.core" :msg "/ok") ;;=> user.core/ok
(tap :ns "user.core") ;;=> user.core/nothing

Multimethods

 Multi-Arity函数中我们可以通过入参数目来调用不同的函数实现,但有没有一种如C#、Java那样根据入参类型来调用不同的函数实现呢?clj/cljs为我们提供Multimethods这一杀技——不但可以根据类型调用不同的函数实现,还可以根据以下内容呢!

  1. 类型
  2. 属性
  3. 元数据
  4. 入参间关系

 想说"Talk is cheap, show me the code"吗?在看代码前,我们先看看到底Multimethods的组成吧
1.dispatching function
 用于对函数入参作操作,如获取类型、值、运算入参关系等,然后将返回值作为dispatching value,然后根据dispatching value调用具体的函数实现。

;; 定义dispatching function
(defmulti name docstring? attr-map? dispatch-fn & options);; 其中options是键值对
;; :default :default,指定默认dispatch value的值,默认为:default
;; :hierarchy {},指定使用的hierarchy object

2.method
 具体函数实现

;; 定义和注册新的函数到multimethod
(defmethod multifn dispatch-val & fn-tail)

3.hierarchy object
 存储层级关系的对象,默认情况下所有相关的Macro和函数均采用全局hierarchy object,若要采用私有则需要通过(make-hierarchy)来创建。

还是一头雾水?上示例吧!
示例1 —— 根据第二个入参的层级关系

(defmulti area(fn [x y]y))(defmethod area ::a[x y](println "derive from ::a"))
(defmethod area :default[x y](println "executed :default"))(area 1 `a) ;;=> executed :default
(derive `a :a)
(area 1 `a) ;;=>derive from ::a

示例2 -- 根据第一个入参的值

(defmulti area(fn [x y]x))(defmethod area 1[x y](println "x is 1"))
(defmethod area :default[x y](println "executed :default"))(area 2 `a) ;;=> executed :default
(area 1 :b) ;;=> x is 1

示例3 -- 根据两入参数值比较的大小

(defmulti area(fn [x y](> x y)))(defmethod area true[x y](println "x > y"))
(defmethod area :default[x y](println "executed :default"))(area 1 2) ;;=> executed :default
(area 2 3) ;;=> x > y

 删除method

;; 函数签名
(remove-method multifn dispatch-val);; 示例
(remove-method area true)

分发规则

 先对dispatching value和method的dispatching-value进行=的等于操作,若不匹配则对两者进行isa?的层级关系判断操作,就这样遍历所有注册到该multimethod的method,得到一组符合的method。若这组method的元素个数有且仅有一个,则执行该method;若没有则执行:default method,若还是没有则抛异常。若这组method的元素个数大于1,且没有人工设置优先级,则抛异常。
 通过prefer-method我们可以设置method的优先级

(derive `a `b)
(derive `c `a)(defmulti test(fn [x](x)))(defmethod test `a[x](println "`a"))(defmethod test `b[x](println "`b"));; (test `c) 这里就不会出现多个匹配的method
(prefer-method `a `b)
(test `c) ;;=> `a

层级关系

 层级关系相关的函数如下:

;; 判断层级关系
(isa? h? child parent)
;; 构造层级关系
(derive h? child parent)
;; 解除层级关系
(underive h? child parent)
;; 构造局部hierarchy object
(make-hierarchy)

上述函数当省略h?时,则操作的层级关系存储在全局的hierarchy object中。
注意:层级关系存储在全局的hierarchy object中时,Symbole、Keyword均要包含命名空间部分(即使这个命名空间并不存在),否则会拒绝。

(ns cljs.user);; Symbole, `b会展开为cljs.user/b
(derive 'dummy/a `b)
;; Keyword, ::a会展开为cljs.user/:a
(derive ::a ::b)

另外还有parentancestorsdescendants

(derive `c `p)
(derive `p `pp);; 获取父层级
(parent `c) ;;=> `p
;; 获取祖先
(ancestors `c) ;;=> #{`p `pp}
;; 获取子孙
(descendants `pp) ;;=> #{`p `c}

局部层级关系

 通过(make-hierarchy)可以创建一个用于实现局部层级关系的hierarchy object

(def h (make-hierarchy))
(def h (derive h 'a 'b))
(def h (derive h :a :b))(isa? h 'a 'b)
(isa? h :a :b)

注意:局部层级关系中的Symbol和Keyword是可以包含也可以不包含命名空间部分的哦!

Condition Map

 对于动态类型语言而言,当入参不符合函数定义所期待时,是将入参格式化为符合期待值,还是直接报错呢?我想这是每个JS的工程师必定面对过的问题。面对这个问题我们应该分阶段分模块来处理。

  1. 开发阶段,对于内核模块,让问题尽早暴露;
  2. 生产阶段,对于与用户交互的模块,应格式化输入,并在后台记录跟踪问题。
     而clj/cljs函数中的condition map就是为我们在开发阶段提供对函数入参、函数返回值合法性的断言能力,让我们尽早发现问题。
(fn name [params*] condition-map? exprs*)
(fn name ([params*] condition-map? exprs*)+); condition-map? => {:pre [pre-exprs*]
;                    :post [post-exprs*]}
; pre-exprs 就是作为一组对入参的断言
; post-exprs 就是作为一组对返回值的断言

示例

(def mysum(fn [x y]{:pre  [(pos? x) (neg? y)]:post [(not (neg? %))]}(+ x y)))(mysum 1 1)  ;; AssertionError Assert failed: (neg? y)  user/mysum
(mysum -1 1) ;; AssertionError Assert failed: (pos? x)  user/mysum
(mysum 1 -2) ;; AssertionError Assert failed: not (neg? %))  user/mysum

 在pre-exprs中我们可以直接指向函数的入参,在post-exprs中则通过%来指向函数的返回值。
 虽然增加函数执行的前提条件,而且可以针对函数的值、关系、元数据等进行合法性验证,但依旧需要在运行时才能触发验证(这些不是运行时才触发还能什么时候能触发呢?)。对动态类型语言天然编译期数据类型验证,我们可以通过core.typed这个项目去增强哦!

总结

 现在我们可以安心把玩函数了,oh yeah!
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/7137597.html ^_^肥仔John

(cljs/run-at (JSVM. :all) 细说函数)相关推荐

  1. Kotlin let、with、run、apply、also函数的使用

    let,with,run,apply,also 是内联扩展函数 下面是自己使用的心的如果有错的地方希望给予指正谢谢 这几个主要用来简化操作,使得代码可读性提高 ,下面列举项目中使用效果 1 let 先 ...

  2. golang 相互引用_golang go run undefined 同一个package中函数互相调用的问题

    golang中同一个package中函数互相调用的问题 同一个packge中(test) a.go package main func main(){ Test() } b.go package ma ...

  3. Kotlin特色之object、let、with、run、apply、also函数的使用

    前言 相比Java,Kotlin提供了不少高级语法特性.对于一个Kotlin的初学者来说经常会写出一些不够优雅的代码.在Kotlin中的源码标准库(Standard.kt)中提供了一些Kotlin扩展 ...

  4. Kotlin中的also、let、run、with、apply函数的用法

    在Kotlin中有几个十分相似的标准库函数,他们之间也有一些差异,如果使用不当可能回得到与预期相反的效果,所以我们来简短的区分一下let.also.run.with.apply 这5个标准库函数的区别 ...

  5. Android Kotlin之let、with、run、apply、also函数的使用

    前言 一.Kotin的lambda 二.let函数 三.with函数 四. run函数 五. apply函数 六. also函数 七.总 前言 相比Java语言Kotlin更加简洁安全高效.不仅支持l ...

  6. Kotlin系列之let、with、run、apply、also函数的使用

    标签: Kotlin      常用技巧 目录: 一.回调函数的Kotin的lambda的简化 二.内联扩展函数之let 三.内联函数之with 四.内联扩展函数之run 五.内联扩展函数之apply ...

  7. (cljs/run-at (JSVM. :browser) 简单类型可不简单啊~)

    前言  每逢学习一个新的语言时总要先了解这门语言支持的数据类型,因为数据类型决定这门语言所针对的问题域,像Bash那样内置只支持字符串的脚步明显就是用于文本处理啦.而数据类型又分为标量类型(Scala ...

  8. 内联函数let、also、with、run、apply的用法

    let 在函数体内使用 it 代替调用者访问其公有的属性和方法 val person = Person("fzh", 22)person.let {print("name ...

  9. C++虚函数表实现详细解析 (附示例)

    目录 前言 运行环境 什么是虚函数 简单示例 虚函数的实现原理 虚函数表 通过虚表找到函数地址并调用 虚函数表的具体分析 无继承关系时的虚函数表 单继承但未重写虚函数时的虚函数表 单继承且重写虚函数时 ...

最新文章

  1. 第4章 管道与FIFO
  2. 【玩转Ubuntu】01. Ubuntu上配置JDK
  3. 可用于在 Microsoft.NET Framework 4.0 中的 ASP.NET 浏览器定义文件的修补程序
  4. 掌握Java 11的Constantdynamic
  5. 想换机的再等等!低价iPhone9或将推迟发布
  6. python将网页保存为图片_使用Python保存网页上的图片或者保存页面为截图
  7. webkit内核浏览器的CSS写法
  8. python爆破端口_挑战全网多线程批量扫描爆破弱办事端口工具,Python制造专属!...
  9. python学来干什么-学 Python 都用来干嘛的?
  10. mysql常见内置函数
  11. sqlserver 中怎样查看一个数据库中表的关系
  12. Unity文件操作路径
  13. 数据结构——拓扑排序算法理解和实现
  14. 电脑鼠标右键菜单太多了怎么办?Windows右键菜单清理删除方法
  15. BZOJ3717 [PA2014]Pakowanie
  16. 什么是蜂窝移动网络?
  17. linux中ifi_info的英文全称,linux下里面如何获取网卡的实时网速
  18. 网格计算, 云计算, 集群计算, 分布式计算, 超级计算
  19. canvas火焰效果
  20. 阿里企业邮箱526 Authentication failure[0]

热门文章

  1. 使用 CXF 做 webservice 简单例子
  2. linux添加启动脚本文件夹,linux – 将脚本中的符号链接添加到rc.d文件夹中以在系统启动期间启动进程...
  3. mysql账号密码忘_mysql用户名密码忘记了解决方法
  4. 炸窝Vector简介
  5. PCB布局,布线技巧总结
  6. 《Python Cookbook 3rd》笔记(5.8):固定大小记录的文件迭代
  7. getdevicecaps在哪个头文件里_一招定胜负,while (true) 和 for (;;) 到底哪个更快
  8. Ubuntu通过可视化界面配置 查找IP地址不存在的解决办法
  9. MAC查找JDK的路径
  10. DCT(离散余弦变换(DiscreteCosineTransform))