(cljs/run-at (JSVM. :all) "细说函数")
前言
作为一门函数式编程语言,深入了解函数的定义和使用自然是十分重要的事情,下面我们一起来学习吧!
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;若没有则执行: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)
另外还有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) "细说函数")的更多相关文章
- Kotlin 之 let、with、run、apply、also 函数的使用
一.内联拓展函数 let let 扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择:let函数另一个作用就是可以避免写一些判断null的操 ...
- QtConcurrent::run 运行类的成员函数
https://stackoverflow.com/questions/2152355/is-it-possible-to-use-qtconcurrentrun-with-a-function-me ...
- (cljs/run-at (JSVM. :browser) "简单类型可不简单啊~")
前言 每逢学习一个新的语言时总要先了解这门语言支持的数据类型,因为数据类型决定这门语言所针对的问题域,像Bash那样内置只支持字符串的脚步明显就是用于文本处理啦.而数据类型又分为标量类型(Scala ...
- 细说javascript函数
javascript函数是一个比较奇怪的东西,接触一段时间你就会犯迷糊,弄不明白它到底是什么了.你是否会因为有的javascript函数没有名字而莫名其妙,是否会因为javascript函数的参数没有 ...
- 在不开启事件循环的线程中使用QTimer(QThread::run函数自带事件循环,在构造函数里创建线程,是一种很有意思的线程用法) good
引入 QTimer是Qt自带的定时器类,QTimer运行时是依赖于事件循环的,简单来说,在一个不开启事件循环(未调用exec() )的线程中,QTimer是无法使用的.通过分析Qt源码可发现,调用QT ...
- Run Loop详解
Run loops是线程的基础架构部分.一个run loop就是一个事件处理循环,用来不停的调配工作以及处理输入事件.使用run loop的目的是使你的线程在有工作的时候工作,没有的时候休眠. Run ...
- Linux wait函数详解
wait和waitpid出现的原因 SIGCHLD --当子进程退出的时候,内核会向父进程SIGCHLD信号,子进程的退出是个异步事件(子进程可以在父进程运行的任何时刻终止) --子进程退出时,内核将 ...
- 李洪强iOS开发之OC[017]函数和方法的区别
// // main.m // 15 - 函数和对象的方法的区别 // // Created by vic fan on 16/7/12. // Copyright © 2016年 李洪强. ...
- C++之------回调函数
一:What?(什么是回调函数) 回调函数图文讲解 谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数. 例如Win32 ...
随机推荐
- 浅析Web缓存
在前端开发中,性能一直都是被大家所重视的一点,然而判断一个网站的性能最直观的就是看网页打开的速度.其中提高网页反应速度的一个方式就是使用缓存.一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并 ...
- Lesser known purrr tricks
purrr is package that extends R's functional programming capabilities. It brings a lot of new stuff ...
- jq、js中判断checkbox是否选中
最近在开发项目时用到checkbox复选框,其中遇到一个问题:在JQ中如何判断checkbox是否被选中呢?之前用JQ获取元素的属性用的都是attr(),但用在checkbox上却没有用,原因何在?? ...
- 基于类(Java)和基于原理(JavaScript)的对象系统的比较
Java:面向对象编程语言,吸收了C++语言的各种优点,丢掉了C++让人头疼的多继承.指针等概念.具有功能强大和简单易用的两大特征.Java具有简单性.面向对象.分布式.健壮性.安全性.平台独立与可移 ...
- 初入计算机图形学——BVH结构的实现
摘要: 本人水平有限,若有错误也请指正~ 光线追踪作为全局光照解决方案的一个重要思想,其与场景大量三角形的求交效率高低直接影响最终算法的速度,典型的一些渲染器都采用二叉树来将场景进行划分,最近自己实现 ...
- Gradle入门学习---认识buildeTypes和dependencies
Gradle是Android Studio默认的构建工具,如果是基本的APP开发,不会涉及到Gradle太多内容,毕竟它的诞生就不是专为Android服务的. 日常开发需要涉及到使用Gradle的场景 ...
- 搭建rtmp直播流服务之2:使用java实现ffmpeg命令接口化调用(用java执行ffmpeg命令)
欢迎大家积极开心的加入讨论群 群号:371249677 (点击这里进群) 一.环境搭建 1.安装ffmpeg 下载对应系统的ffmpeg安装包,个人采用windows平台进行开发,所以安装了windo ...
- 扔掉log4j、log4j2,自己动手实现一个多功能日志记录框架,包含文件,数据库日志写入,实测5W+/秒日志文件写入,2W+/秒数据库日志写入,虽然它现在还没有logback那么强大
讲到log4j,现在国外基本是没有开发者用这个框架了,原因大致有几点,1.功能太少:2.效率低下:3.线程锁bug等等等各种莫名其妙的bug一直都没解决. 其实最重要的是log4j的作者自己也放弃了l ...
- 用hmmlearn学习隐马尔科夫模型HMM
在之前的HMM系列中,我们对隐马尔科夫模型HMM的原理以及三个问题的求解方法做了总结.本文我们就从实践的角度用Python的hmmlearn库来学习HMM的使用.关于hmmlearn的更多资料在官方文 ...
- matplotlib.pyplot.hist
**n, bins, patches = plt.hist(datasets, bins, normed=False, facecolor=None, alpha=None)** ## 函数说明 用于 ...