(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))

注意:

  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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454819.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Request的getHeader()和getParameter()的区别

区别是&#xff1a;一个是获得HTTP头信息,一个是获得表单参数值。转载于:https://www.cnblogs.com/pxffly/p/7460514.html

gcc中的内嵌汇编语言(Intel i386平台)

gcc中的内嵌汇编语言&#xff08;Inteli386平台&#xff09; 一.声明 虽然Linux的核心代码大部分是用C语言编写的&#xff0c;但是不可避免的其中还是有一部分是用汇编语言写成的。有些汇编语言代码是直接写在汇编源程序中的&#xff0c;特别是Linux的启动代码部分&#xff1b…

数据库学习,树形结构的数据库表Schema设计方案

2019独角兽企业重金招聘Python工程师标准>>> 程序设计过程中&#xff0c;我们常常用树形结构来表征某些数据的关联关系&#xff0c;如企业上下级部门、栏目结构、商品分类等等&#xff0c;通常而言&#xff0c;这些树状结构需要借助于数据库完成持久化。然而目前的各…

[转载] 手工制作Win7 OEM版

只要往微软MSDN原版ISO的sources目录加个“$OEM$”文件夹&#xff0c;再删除sources下面的ei.cfg文件就可以了。 来源&#xff1a;http://zxkh19501.blog.163.com/blog/static/1237851792010629113427594/转载于:https://www.cnblogs.com/784040932/p/win7oem.html

mysql dbo_mysql-双重分组

我的表有两列&#xff1a;名称和等级.看起来像这样&#xff1a;NAME | GRADEAdam | 1Adam | 2Adam | 2Adam | 3Frank | 2Frank | 1现在,我想创建如下所示的视图&#xff1a;NAME | GRADE 1 | GRADE 2 | GRADE 3Adam | 1 | 2 | 1Frank | 1 | 1 | 0我写了这个&#xff1a;SELECT …

课堂作业整理三 (集合:list接口)

集合中 list的方法列表&#xff08;Arraylist和Linkedlist&#xff09; 方法名功能说明ArrayList()构造方法&#xff0c;用于创建一个空的数组列表add&#xff08;E&#xff0c;e&#xff09;将指定的元素添加到此列表的尾部get&#xff08;int index&#xff09;返回此列表中指…

LINUX系统移植(史上最全最细,强烈推荐)

Linux系统移植 目 录 第一部分 前言...................................................................................................................................8 1 硬件环境................................................................................…

The serializable class XXX does not declare a static final serialVersionUID field of type long的警告...

原文: http://blog.csdn.net/ultrakang/article/details/41820543转载于:https://www.cnblogs.com/Baronboy/p/7465508.html

Ubuntu17.04 之 systemd 设置开机启动

Ubuntu从16.04开始不再使用 initd 管理系统&#xff0c;改用 systemd。 和 Centos 一样&#xff0c;升级到 Centos7 之后使用 systemd 替代 init.d 为了像以前一样&#xff0c;在/etc/rc.local中设置开机启动程序&#xff0c;需要以下几步&#xff1a; 1、链接文件 systemd 默…

replaceselection();java'_Java JTextComponent.replaceSelection方法代码示例

import javax.swing.text.JTextComponent; //导入方法依赖的package包/类public void actionPerformed(final ActionEvent evt, final JTextComponent target) {if (target ! null) {if (!target.isEditable() || !target.isEnabled()) {target.getToolkit().beep();return;}Ed…

Systemd 入门教程之命令篇

Systemd 是 Linux 系统工具&#xff0c;用来启动守护进程&#xff0c;已成为大多数发行版的标准配置。 本文介绍它的基本用法&#xff0c;分为上下两篇。今天介绍它的主要命令&#xff0c;下一篇介绍如何用于实战。 一、由来 历史上&#xff0c;Linux 的启动一直采用init进程。…

GCC生成的汇编代码

假设我们写了一个C代码文件 code.c包含下面代码&#xff1a; int accum 0; int sum(int x, int y) { int t x y; accum t; return t; } 这是用echo命令输入源码的效果&#xff0c;简单的就是最好的&#xff1a;&#xff09;一、查看GCC生成的汇编代码在命令行…

php __FILE__,__CLASS__等魔术变量,及实例

php __FILE__,__CLASS__等魔术变量,及实例 今天看到一个魔术变量&#xff0c;是以前没见过的&#xff0c;__DIR__&#xff0c;我查了查&#xff0c;发现原来是php5.3新增的&#xff0c;顺便举几个例子&#xff0c;解释一下php的魔术变量 1&#xff0c;__FILE__ 文件的完整路径和…

java虚方法和抽象方法_虚方法和抽象方法--基础回顾

抽象方法是只有定义、没有实际方法体的函数&#xff0c;它只能在抽象函数中出现&#xff0c;并且在子类中必须重写&#xff1b;虚方法则有自己的函数体&#xff0c;已经提供了函数实现&#xff0c;但是允许在子类中重写或覆盖。重写的子类虚函数就是被覆盖了。抽象方法使用abst…

jQuery高度及位置操作

1. 获取滑轮位置&#xff0c;scrolltop:上下滚动的意思。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div style"height:100px;width:10…

you have mixed tabs and spaces fix this

http://blog.csdn.net/tonyyan19781/article/details/60882443Vs2013 IDE下&#xff0c;编辑C的工程源码&#xff0c;在打开文件的时候&#xff0c;会出现 “ you have mixed tabs and spaces fix this ”&#xff0c; 然后给出三个选项 Tabify、Untabify、Dont show again。尤…

Systemd 入门教程之实战篇

一、开机启动 对于那些支持 Systemd 的软件&#xff0c;安装的时候&#xff0c;会自动在/usr/lib/systemd/system目录添加一个配置文件。 如果你想让该软件开机启动&#xff0c;就执行下面的命令&#xff08;以httpd.service为例&#xff09;。$ sudo systemctl enable httpd上…

从VC++到GCC移植:谈两者的语法差异

从VC到GCC移植&#xff1a;谈两者的语法差异 许式伟 &#xff08;版权声明&#xff09; 2007-1-28 类型引用 template <classT>classFoo { typedef T::SomeType SomeType; };这段代码在VC中一点问题也没有&#xff0c;但是GCC并不允许&#xff0c;因为它不知道T::S…

牛客网Java刷题知识点之关键字static、static成员变量、static成员方法、static代码块和static内部类...

不多说&#xff0c;直接上干货&#xff01; 牛客网Java刷题知识点之关键字static static代表着什么 在Java中并不存在全局变量的概念&#xff0c;但是我们可以通过static来实现一个“伪全局”的概念&#xff0c;在Java中static表示“全局”或者“静态”的意思&#xff0c;用来修…

30天自制操作系统(二)汇编语言学习与Makefile入门

1 介绍文本编辑器这部分可直接略过2 继续开发helloos.nas中核心程序之前的内容和启动区以外的内容先不讲了&#xff0c;因为还涉及到一些软盘方面的知识。然后来讲的是helloos.nas这个文件; hello-os ; TAB4ORG 0x7c00 ; 指明程序的装载地址; 以下这部分记录…