zoukankan      html  css  js  c++  java
  • 【转】OCaml程序的结构

    出自:http://www.nirvanastudio.org/ocaml/the-structure-of-ocaml-programs.html

    原文地址: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(如上面例子所示)。

    
  • 相关阅读:
    古代规模最大的战争:长平之战(做事不能太小气,不同的将领有不同的视角,要智胜,活着很重要)
    聚集索引更新后会不会马上重新排序
    GitHub Pages 搭建流程-基于jekyll-bootstrap
    OpenStack调研
    领域模型设计
    Load ContextCLR 探测
    Sql Server Job 简单使用
    Power Designer导出实体类和NHibernate xml文件
    解决跨域
    性能计数器
  • 原文地址:https://www.cnblogs.com/njucslzh/p/2032426.html
Copyright © 2011-2022 走看看