原文地址:http://www.ocaml-tutorial.org/the_structure_of_ocaml_programs 翻译:ShiningRay
现在我们花些时间从一个更高的层次来看看实际的OCaml程序。我想教一下关于局部和全局定义,什么时候使用;;
,什么时候是;
,以及模块、嵌套函数和引用。这样我们就要看这些以前毫无概念没见过的OCaml概念了,不过现在还不用担心细节问题。首先关注程序整体结构以及我指出的一些特点。
局部“变量”(实际上是 局部表达式,local expressions)
让我们先拿average
函数看一下,并在C语言中添加一个局部变量(可以拿它和我们之前第一次的定义进行一下比较)。
double
average (double a, double b)
{
double sum = a + b;
return sum / 2;
}
现在我们在OCaml的版本中也做同样的事情:
let average a b =
let sum = a +. b in
sum /. 2.0;;
# let average a b= (a+.b)/.2.0;; 注意优先级别.
val pjz : float -> float -> float = <fun>
# pjz 10.5 21.5;;
- : float = 16.
#
标准短语let name = expression in
可以用于定义一个局部表达式,然后name
就可以在后面的函数中使用,来替代expression
了,直到结束该代码块的;;
出现。注意在in
后面我们并没有缩进。就把let...in
当作是一个语句。
现在将C的局部变量和这些命名的局部表达式进行比较,好像是差不多的。其实他们是两种不同的东西。C变量sum
在栈上有一个分配给它的槽。如果需要,你可以在函数中以后给sum
分配值,甚至可以获取sum
的地址。但是这对于OCaml版本却不正确。在OCaml版本中,sum
只是表达式a +. b
的缩写。不可能对sum
赋值或者改变它的值。(稍后会给你看如何才能制造真正的变量)。
下面是另一个例子,可以把事情讲的更加清楚。下面两个代码片断将返回同样的值(也就是 (a+b) + (a+b)2):
let f a b =
(a +. b) +. (a +. b) ** 2.
;;
let f a b =
let x = a +. b in
x +. x ** 2.
;;
第二个版本可能会快一点(不过现在大多数编译器都应该可以直接为你完成“消除重复子表达式”工作),同时它也更加容易阅读。第二个例子中的x
仅仅是a +. b
的缩写。
全局“变量”(实际上是全局表达式)
你也可以在最顶层为某些东西定义全局名字,同时与上面我们所说的局部“变量”一样,这些都完全不是真正的变量,仅仅是某些东西的别名。下面是一个实际应用的例子(做了删减):
let html =
let content = read_whole_file file in
GHtml.html_from_string content
;;
let menu_bold () =
match bold_button#active with
true -> html#set_font_style ~enable:[`BOLD] ()
| false -> html#set_font_style ~disable:[`BOLD] ()
;;
let main () =
(* 代码省略 *)
factory#add_item "Cut" ~key:_X ~callback: html#cut
;;
在这段实际的代码中,html
是一个HTML编辑部件(来自lablgtk库的一个对象),它是由第一行语句let html=
一次性在程序开始的时候创建的。然后在后面的函数中被多次引用。
注意上面的代码段中的html
名字不能当作是一个和C或者其他命令式语言中的实际的全局变量。并没有为“html
指针”分配任何空间进行“存储”。也不能对html
分配任何值,例如重新将其分配指向另一个不同的部件。在下面的一节中,我们将讨论引用,这才是真正的变量。
Let-绑定
任何let ...
的使用,无论在最顶层(全局的),或在一个函数中,一般都称之为let-绑定。
引用:真正的变量
如果你需要一个真正的变量对其进行赋值,并可在程序中使用、更改,那要怎样呢?这时候就需要用到引用(reference)。引用和C/C++中的指针十分类似。在Java中,所有保存对象的变量实际上都是对象的引用(指针)。在Perl中,引用就是引用——和OCaml中的是同一个东西。
下面是我们如何在OCaml中创建指向一个int
值得引用:
ref 0;;
事实上,这个语句并没有什么大的用途。我们仅仅创建了一个引用,但因为我们并没有给它命名,所以垃圾收集器会过来将其立刻回收!(实际上,它也可能在编译期就被扔掉了)。让我们来给这个引用命名吧:
let my_ref = ref 0;;
# let myvar= ref "hello";;
val myvar : string ref = {contents = "hello"}
#
这个引用目前存储了一个整数零。下面我们再将一些别的东西放进去(赋值):
my_ref := 100;;
同时看看现在这个引用里面包含什么:
# !my_ref;;
- : int = 100
显示变量数值
# !myvar
;;
- : string = "hello"
#
所以,:=
操作符是用于为引用赋值的,同时!
操作符可以解除引用获取实际的内容。 下面是一个与C/C++大致的比较:
OCaml C/C++
let my_ref = ref 0;; int a = 0; int *my_ptr = &a;
my_ref := 100;; *my_ptr = 100;
!my_ref *my_ptr
引用有他们的用途,但是你也可能发现并不会经常用到引用。更多的时候,你会在函数定义中使用let name = expression in
来命名局部表达式。
嵌套函数
C实际上并没有嵌套函数的概念。GCC支持C程序的嵌套函数,但我还不知道有什么程序会实际使用这个扩展。不管怎样,先看看gcc info页面是如何解释嵌套函数的:
一个“嵌套函数”是指定义在另一个函数中的函数。(GNU C++并不支持嵌套函数。)嵌套函数的名称是局限于它所定义的代码块中的。例如,下面我们定义一个叫做`square'的嵌套函数,并调用两次:
foo (double a, double b)
{
double square (double z) { return z * z; }
return square (a) + square (b);
}
嵌套函数可以访问任何包含它的函数中定义时所能看到的变量。这叫做“词法范围”(lexical scoping)。例如,下面我们展示一下使用了叫做`offset'的继承了的变量的嵌套函数:
bar (int *array, int offset, int size)
{
int access (int *array, int index)
{ return array[index + offset]; }
int i;
/* ... */
for (i = 0; i < size; i++)
/* ... */ access (array, i) /* ... */
}
你应该有点明白了。不过,嵌套函数在OCaml中是十分有用而且十分常用的。下面是从一些实际应用代码中截取的嵌套函数的例子:
let read_whole_channel chan =
let buf = Buffer.create 4096 in
let rec loop () =
let newline = input_line chan in
Buffer.add_string buf newline;
Buffer.add_char buf '\n';
loop ()
in
try
loop ()
with
End_of_file -> Buffer.contents buf;;
先无需关心这段代码干了什么——它还包含了尚未在本教程中讨论的很多概念。先关注中间的叫做“loop
”的嵌套函数,它只有一个单元参数。你可以调用在函数read_whole_channel
中调用loop ()
,但它并没有在这个函数外边定义。嵌套函数可以访问定义在主函数中的变量(这里loop
可以访问局部名称buf
)。
嵌套函数的形式和局部命名表达式的形式是一样的:let name 形参 = 函数定义 in
。
一般来说,你要将在新的一行上缩进函数定义,如上面的例子所示,同时记住如果函数是递归的,要使用let rec
而非let
(如上面例子所示)。
来自(http://hi.baidu.com/cg51/blog/item/db200bf49ef2e16fddc47407.html)
分享到:
相关推荐
标题 "用OCaml开发的国际象棋程序" 暗示了这个项目是使用OCaml编程语言实现的一个国际象棋游戏。OCaml是一种静态类型的、强类型、函数式编程语言,它结合了面向对象和过程编程的特点,具有高效、安全和模块化的优势...
这个库提供了一套完整的数据结构集合,旨在帮助OCaml开发者在编写程序时更加方便地处理和操作数据。 OCaml是一种多范式、静态类型的编程语言,它支持函数式、面向对象和命令式编程风格。在OCaml中,数据结构是编程...
6. **异常处理**:OCaml的异常处理机制允许程序在遇到错误时抛出异常,并在适当的地方捕获和处理。 7. **内存管理**:OCaml使用自动垃圾回收机制管理内存,确保了程序不会因为内存泄漏而崩溃。 8. **泛型编程**:...
介绍了如何在函数定义中使用模式匹配,这是OCaml中一项非常强大的特性,可以极大地简化代码并提高程序的可读性。 **模式表达式** 讲解了模式匹配的基本语法,包括如何匹配不同的数据类型。 **不完整匹配** 当...
4. 多项式的操作:这说明本书有讲解数学中常用数据结构的操作,例如如何在OCaml中实现对多项式的操作,这可能包括创建、修改、计算多项式等问题。 5. 字符和字符操作:作为编程语言的基础,字符和字符串的处理也是...
笔记中不仅包含了OCaml程序的解释器来说明操作语义,还探讨了模块化或面向对象编程模式的实际编程情境,并用类型检查问题进行解释。 5. 程序练习:课程笔记提供了不同难度的练习题,以帮助读者检查理解并训练其操作...
### OCaml从零开始:全面解析 #### 一、OCaml简介 ...通过本书的学习,你将掌握OCaml的基础知识,并能编写出具有一定复杂度的程序。此外,本书丰富的习题和解答也能帮助你更好地理解和运用所学知识。
- **循环**:虽然不鼓励使用,但OCaml仍然提供了循环控制结构,如 `while` 和 `for` 循环。 **1.6 异常处理** - **异常机制**:OCaml提供了一套完整的异常处理机制,支持 `try`, `with` 和 `raise` 关键字。 - **...
OMicroB 是一个 OCaml 虚拟机,专门用于在资源非常有限的设备上运行 OCaml 程序,例如 AVR Atmega32u4 微控制器(2.5 ko RAM)。 OMicroB 执行多次静态分析,以减少生成的最终可执行文件。 要求 OPAM2 + OCaml (>=...
在介绍完编程风格后,作者进一步讲解了如何运行OCaml程序。OCaml提供了三种主要的执行方式:交互式解释器(Top-level)、字节码编译和本机代码编译。每种方式都有其适用场景: - **交互式解释器(Top-level)**:...
在与OCaml结合使用时,可能是指编写文档或者报告,解释如何分析和修复程序中的错误,尤其是对于那些涉及到复杂数据结构和算法的输入错误。 "rite-master" 可能是一个Git仓库的克隆,通常包含项目的源代码、文档、...
它可以用于从 CSV 文件导入数据到 OCaml 数据结构,进行预处理或分析,然后将结果导出回 CSV 文件。配合 OCaml 的其他库如 `core` 或 `batteries`,可以构建强大的数据处理流水线。 **标签关联** - **science**:`...
它涵盖了OCaml的基本语法、数据类型、控制结构等核心概念,为后续章节的学习奠定了坚实的基础。 #### 三、“Fold”的深入理解 **第1章**介绍了“Fold”,这是函数式编程中的一个非常重要的概念。Fold允许我们通过...
`ppx_protocol_conv` 的支持使得 OCaml 程序可以轻松处理这种紧凑的二进制格式。 7. **Library**:`ppx_protocol_conv` 本身就是一个 OCaml 库,可以方便地集成到你的项目中,提供强大的序列化和反序列化能力。 ...
OCaml的模式匹配是其语法的一大亮点,它允许程序员根据值的结构来分解值并执行不同的操作。这种特性使得代码更具可读性和简洁性。 5. **GADTs(Generalized Algebraic Data Types)**: OCaml支持通用代数数据...
`postgresql-ocaml` 库实现了 PostgreSQL C API 的 OCaml 绑定,允许开发者在 OCaml 程序中直接调用 PostgreSQL 的函数。这些绑定包括连接管理、查询执行、结果集处理、事务控制等功能。通过这个库,开发者可以使用 ...