OCaml入门(5)

匿名函数

函数式语言中,函数是一等公民,它应该可以像其它类型(int,float,string等)一样,能支持“字面量”,这就是匿名函数。

utop # (fun x -> x + 1);;
- : int -> int = <fun>
utop # (fun x -> x + 1) 7;;
- : int = 8 

可以看到,匿名函数可以代替函数的位置来使用。

有些函数需要的参数是另一个函数,当然也可以用匿名函数传入。比如:

utop # List.map ~f:(fun x->x+1) [1;2;3];;
- : int list = [2; 3; 4]

不要太在意: ~f: 这样的语法,这只是具名函数调用的用法。大多数语言中,函数的调用都是按位置来匹配参数的。

这样做的缺点是容易忘记哪个参数在哪个位置。Ocaml当然也支持按位置传参调用函数的方式,但同时鼓励按名字传参来调用函数,这样的程序更具可读性。

(你可能会觉得这多别扭啊,暂且忍受,人们往往都是习惯的奴隶)

匿名函数当然能绑定到一个符号,这样就成了具名函数:

utop # let plusone = fun x -> x + 1;;
val plusone : int -> int = <fun>
utop # plusone 5;;
- : int = 6   

这种定义如此普遍,Ocaml提供了语法糖来简化:

utop # let plusone x = x + 1;;
val plusone : int -> int = <fun>  

这与上一个定义是完全一样的,只是语法偷懒了而已。

匿名函数也可以有多个参数:

utop # let diff x y = abs (x - y);;
val diff : int -> int -> int = <fun>
utop # diff 5 8;;
- : int = 3   

你可能会很奇怪这个函数的类型签名: int -> int -> int

在我们的想象中,应该是:(int, int)-> int 才更合理啊。

这正是函数式语言更数学化的特征之一。在数学上,严格地说,每个函数都只能传入一个参数,并且返回一个参数。

如果有一个函数看起来传入两个参数,它只不过是传入一个参数又返回了另一个函数的函数。

也就是说,本质上,上边的函数定义等价于:

utop # let diff =
fun x -> (fun y -> abs (x - y));;
val diff : int -> int -> int = <fun>  

这是个脑力体操,仔细想想吧。

函数式语言的威力恰在于:函数可以作为参数,函数可以作为返回值。

按照这个思路,我们顺理成章地可以这样写:

utop # let diff3 = diff 3;;
val diff3 : int -> int = <fun>
utop # diff3 8;;
- : int = 5     

对于多参的函数,只提供部分参数的做法很普遍,数学上叫做:偏参函数。

函数的递归

函数式语言不鼓励(甚至是不允许)使用变量 + 循环的编程模式,比起循环来,它更青睐于“递归”。

递归就是一函数直接或间接地调用自己。

utop # let rec len lst = match lst with
| [] -> 0
| h::tail -> 1 + len tail;;
val len : ‘a list -> int = <fun>
utop # len [1;2;3;4];;
- : int = 4
utop # len [];;
- : int = 0  

如果一函数是递归的,Ocaml要求必须明确地用 rec来修饰。

rec是recursive的缩写。

如果间接递归就需要一次把多个函数都定义出来,比如: is_even 来判断是否为偶数。这里只是为了示范概念,当然仅仅为这个小功能没必要这样大费周章。

utop # let rec is_even x =
if x=0 then true else is_odd (x-1)
and is_odd x =
if x=0 then false else is_even (x-1);;
val is_even : int -> bool = <fun>
val is_odd : int -> bool = <fun>
utop # is_even 7;;
- : bool = false
utop # is_even 8;;
- : bool = true 

前缀与中缀

一般的函数调用都是前缀格式: 函数 参数 参数 ...

有的时候,两个参数时,中缀更符合习惯。比如: 1 + 2,   5 mod 3

Ocaml中,函数与运算符实在同样的东西,都是: function

中缀运算符如果加上括号,就回到了前缀的风格:

utop # (+) 5 8;;
- : int = 13
utop # (mod) 10 3;;
- : int = 1
utop # List.map ~f:((+) 3) [1;2;3;4];;
- : int list = [4; 5; 6; 7]    

自己定义中缀运算符也可以,必须从如下符号集合中组合:

! $ % & * + - . / : < = > ? @ ^ | ~比如:
utop # let (+!) (x1,y1) (x2,y2) = x1+x2, y1+y2;;
val ( +! ) : int * int -> int * int -> int * int = <fun>
utop # (1,2) +! (10,20);;
- : int * int = (11, 22)

使用含有 * 的运算符需要格外小心,因为 (* 。。。。 *) 表示注释语句。

utop # ( * ) 3 5;;
- : int = 15  

这里括号与星号之间的空格是必须的!

Function

let ff x = match x with ... | ... | ...

这种模式如此普遍,OCaml提供了进一步的简化语法:

utop # let len = function
| [] -> 0
| h::t -> 1 + len t;;
val len : ‘a list -> int = <fun>
utop # len [1;2];;
- : int = 2  

标签参数

多参数函数可以使用标签参数来增加可读性和灵活性。

下面的函数求坡度。

utop # let po ~path ~height = let pi = atan 1.0 *. 4. in
(asin (height /. path)) *. 180.0 /. pi;;
val po : path:float -> height:float -> float = <fun>
utop # po ~path:10. ~height:1.;;
- : float = 5.73917047727  

既然参数又名字,顺序就无关紧要了。

utop # po ~height:1. ~path:10.;;
- : float = 5.73917047727  
时间: 2024-08-30 12:36:52

OCaml入门(5)的相关文章

Ocaml入门(3)

如果在centos环境下配置好了opam,安装好了ocaml, core, utop,接下来就可以开始学习ocaml了. 跟随<Real world Ocaml>的脚步来学吧.网上有html版. 先概览一下. 普通的数字计算 ocaml很在乎类型,浮点数和整数类型运算符不能混用. utop # 3 + 4;; - : int = 7 utop # 3.1 + 4.2;; Error: This expression has type float but an expression was ex

Ocaml入门-1

Ocaml是函数语言.Lisp, Haskell,clojure,scala等也是. 函数式语言的特色是:函数本身是“第一等公民”,它可以被当做参数传递,被当做参数返回. 函数式语言之所以“回暖”可能是因为当前多cpu,分布式,并行计算对软件如何处理并发提出新的挑战. 函数式在并发上有天然优势. 在函数式语言中,Ocaml的主要优势是: 1. 有坚实的学院派理论基础,数学模型严谨. 2. 据称是史上运行速度第二的高级语言(第一毫无疑问是c) 也就是说Ocaml甚至比c++还快. 如何安装ocam

Ocaml入门(2)

假设你已经在centos7上,使用su权限安装好了opam,下面回到普通权限.使用opam来安装其它东西. opam 安装的东西,默认都在 ~/.opam 目录下,它没有权限改变系统目录的东西. 如果配置有问题,很简单.删除 ~/.opam 目录,一切从新来过! 1. opam init 初始化环境,一路yes下去就可以了. 检验是否成功的标准,看环境变量是否添加好: printenv OCAML_TOPLEVEL_PATH 有的时候,可能会需要执行一下如下的指令: eval `opam con

RPC学习----Thrift快速入门和Java简单示例

一.什么是RPC? RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据.在OSI网络通信模型中,RPC跨越了传输层和应用层.RPC使得开发包括网络分布式多程序在内的应用程序更加容易. 二.什么是Thrift? thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发.它结合了功能强大的软件堆栈和

Thrift入门及Java实例演示&lt;转载备用&gt;

Thrift入门及Java实例演示 作者: Michael 日期: 2012 年 6 月 14 日 •概述 •下载配置 •基本概念 1.数据类型 2.服务端编码基本步骤 3.客户端编码基本步骤 4.数据传输协议 •实例演示(java) 1. thrift生成代码 2. 实现接口Iface 3.TSimpleServer服务模型 4.TThreadPoolServer 服务模型 5.TNonblockingServer 服务模型 6.THsHaServer服务模型 7.异步客户端 [一].概述 T

R语言快速上手入门

R语言快速上手入门 课程学习网址:http://www.xuetuwuyou.com/course/196 课程出自学途无忧网:http://www.xuetuwuyou.com 课程简介 本教程深入浅出地讲解如何使用R语言玩转数据.课程中涵盖R语言编程的方方面面,内容涉及R对象的类型.R的记号体系和环境系统.自定义函数.if else语句.for循环.S3类R的包系统以及调试工具等.本课程还通过示例演示如何进行向量化编程,从而对代码进行提速并尽可能地发挥R的潜能.本课程适合立志成为数据科学家的

笔记:Spring Cloud Zuul 快速入门

Spring Cloud Zuul 实现了路由规则与实例的维护问题,通过 Spring Cloud Eureka 进行整合,将自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获取了所有其他微服务的实例信息,这样的设计非常巧妙的将服务治理体系中维护的实例信息利用起来,使得维护服务实例的工作交给了服务治理框架自动完成,而对路由规则的维护,默认会将通过以服务名作为 ContextPath 的方式来创建路由映射,也可以做一些特别的配置,对于签名校验.登录校验等在微服务架构中的冗余问题

linux入门基础知识及简单命令介绍

linux入门基础知识介绍 1.计算机硬件组成介绍 计算机主要由cpu(运算器.控制器),内存,I/O,外部存储等构成. cpu主要是用来对二进制数据进行运算操作,它从内存中取出数据,然后进行相应的运算操作.不能从硬盘中直接取数据. 内存从外部存储中取出数据供cpu运存.内存的最小单位是字节(byte) 备注:由于32的cpu逻辑寻址能力最大为32内存单元.因此32位cpu可以访问的最大内存空间为:4GB,算法如下: 2^32=2^10*2^10*2^10*2^2 =1024*1024*1024

JAVA通信系列二:mina入门总结

一.学习资料 Mina入门实例(一) http://www.cnblogs.com/juepei/p/3939119.html Mina入门教程(二)----Spring4 集成Mina http://www.cnblogs.com/juepei/p/3940396.html Apache Mina 入门实例--创建一个MINA时间服务http://loftor.com/archives/apache-mina-quick-start-guide.html MINA2.0用户手册中文版--系列文