(cljs/run-at (JSVM. :all) "细说函数")
2017-07-10 09:07
357 查看
前言
作为一门函数式编程语言,深入了解函数的定义和使用自然是十分重要的事情,下面我们一起来学习吧!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))
注意:
Lambda表达式的函数体只允许使用一个表达式,因此要通过special form
do来运行多个表达式;
入参symbol为
%1,%2,...%n,当有且只有一个入参时可以使用
%来指向该入参。
Metadata——为函数附加元数据
Symbol和集合均支持附加metadata,以便向编译器提供额外信息(如类型提示等),而我们也可以通过metadata来标记源码、访问策略等信息。对于命名函数我们自然要赋予它Symbol,自然就可以附加元数据了。
其中附加
:private和
defn-定义函数目的是一样的,就是将函数的访问控制设置为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这一杀技——不但可以根据类型调用不同的函数实现,还可以根据以下内容呢!类型
值
属性
元数据
入参间关系
想说"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;若没有则执行
:defaultmethod,若还是没有则抛异常。若这组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)
另外还有
parent、
ancestors和
descendants
(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的工程师必定面对过的问题。面对这个问题我们应该分阶段分模块来处理。开发阶段,对于内核模块,让问题尽早暴露;
生产阶段,对于与用户交互的模块,应格式化输入,并在后台记录跟踪问题。
而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) "一起实现柯里化")
- (cljs/run-at (JSVM. :all) "一次说白DataType、Record和Protocol")
- (cljs/run-at (JSVM. :all) "Metadata就这样哦")
- (cljs/run-at (JSVM. :browser) "搭建刚好可用的开发环境!")
- (cljs/run-at (->JSVM :browser) "语言基础")
- (cljs/run-at (JSVM. :browser) "命名空间就这么简单")
- (cljs/run-at (JSVM. :browser) "简单类型可不简单啊~")
- 【续集】塞翁失马,焉知非福:由 Styles.Render 所引发 runAllManagedModulesForAllRequests="true" 的思考
- MTK模拟器Could not run "cl.exe"错误
- MTK模拟器Could not run "cl.exe"错误
- js跨域调用iframe子页面的函数 提示"没有权限"
- myeclipse10中表单中的JS函数无法写return,onsubmit="return check();"处出错
- myeclipse10中表单中的JS函数无法写return,onsubmit="return check();"处出错
- javascript延时重复执行函数 lLoopRun.js
- 在html的<img src="">中调用js的函数或者js变量来指定图片路径
- 一个模拟"显示桌面.scf"程序的JS小函数
- JS的document.all函数使用示例
- JS中报告"return"语句在函数之外
- js实现"replaceAll"
- 关于JS中的parseInt("099")函数解惑