OCaml 프로그램의 구조

OCaml 프로그램의 구조

지금부터는 실제 OCaml 프로그램 몇 개를 통해 좀 더 높은 차원에서 OCaml을 살펴보려 한다. 지역 정의(definition)와 전역 정의, ;; vs. ;, 모듈(module), 중첩함수(nested function), 레퍼런스(reference)를 배울 것이다. Ocaml의 컨셉을 보면 많이 혼란스러울 수 있는데, 그 이유는 OCaml이 이전에 보지 못한 언어이기 때문이다. 그렇다고 당황할 필요는 없다.(역자주:Ocaml의 문법은 사실 영어로된 수학책을 보는것과 같다.) 대신에 프로그램의 전체적인 생김새와 필자가 강조하는 기능을 숙지하기 바란다.

(역자주 : 아래부터 Top-Level 이라는 것은 일반적으로는 Ocaml의 인터프리터를 의미한다. 나중에 Ocaml에 대해서 자세히 배우게되면 Top-Level의 의미를 알 수 있다. 인터프리터와 컴파일러의 차이는 스스로 공부하기 바란다.)

지역 "변수" (실제로는 지역 표현식)

C 언어로 average 함수에 지역 변수를 하나 더해보자. (일전의 살펴봤던 함수와 비교해 보기 바란다.)

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;;

표준 구문(phrase)인 let name = expression in는 이름 붙은 지역 표현식을 만들어 준다. 블록의 끝을 나타내는 ;;가 나오기 전까지 함수 내에서 expression 대신에 name를 사용할 수 있다. in 뒤에 들여쓰기(indent)가 없음을 주목하자. in를 마치 하나의 문(statement)으로 생각하면 된다. (역자주: 인터프리터에서 함수를 바로 계산하도록 하기 위해선 ;;을 함수 뒤에 붙여준다.)

사실 C의 지역 변수와 이름 붙은 표현식을 비교하는 것은 속임수이다. 사실 둘은 조금 다르다. C의 변수 sum은 스택에 할당된 공간(slot)이 있다. 여러분은 sum에 값을 대입하거나, sum의 주소를 얻어올 수 있다. Ocaml은 이와는 다르다. Ocaml에서 sum은 표현식 a +. b를 대신하는 짧은 이름일 뿐이다. Ocaml에서는 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.
  ;;

두 번째 코드가 더욱 빠른 코드이며(대부분의 컴파일러는 '공통 표현식 제거(common subexpression elimination)' 단계를 통해 이 부분을 자동으로 해줄 수 있어야 한다), 읽기에도 명확하다. 두 번째 예제의 xa +. b의 짧은 이름(shorthand)일 뿐이다.

전역 "변수" (실제로는 전역 표현식)

탑 레벨에 정의되어야 할 내용이 있으면, 여러분은 전역 이름을 정의할 수 있다. 위의 지역 "변수"와 마찬가지로, 이는 실제 변수가 아니라, 단순히 짧은 이름을 뿐이다. 여기 실제 (요약된) 예제가 있다.

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 () =
  (* code omitted *)
  factory#add_item "Cut" ~key:_X ~callback: html#cut
  ;;

이 실제 코드의 일부에서, html은 HTML 수정 위젯(lablgtk 라이브러리의 객체)로, 프로그램 시작 시에 첫 let html =문에 의해서 생성된다. 이 객체는 이후 여러 함수에서 참조된다.

위 코드 예제에서 html은 C나 다른 명령형 언어의 실제 전역 변수와는 비교할 수 없다. "html 포인터"를 "저장"하기 위해 할당된 공간이 없다. 또한 새로운 위젯의 값을 할당한다던지 하는 식으로 html에 어떤 값을 대입할 수도 없다. 다음 절에서 우리는 실제 변수인 레퍼런스에 대해 살펴볼 것이다.

Let 바인딩

let ...을 사용하면, 탑 레벨(전역)에서 사용했든 함수 내에서 사용했든 상관 없이 보통 let-바인딩이라고 부른다.

레퍼런스: 실제 변수

여러분의 프로그램에서 실제로 값을 할당하고 변경시킬 수 있는 실제 변수가 필요하면 어떻게 해야할까? 이 경우 reference가 필요할 것이다. 레퍼런스는 C/C++의 포인터와 매우 유사하다. 자바의 경우, 객체를 저장하는 모든 변수는 실제로는 객체에 대한 레퍼런스(포인터)이다. 펄은 레퍼런스가 레퍼런스이고, OCaml도 마찬가지이다.

다음은 우리가 OCaml에서 int에 대한 레퍼런스를 생성하는 방법이다.

ref 0;;

사실 이 구문은 별로 유용하지 않다. 우리는 레퍼런스를 만들고, 이름을 붙이지 않았기 때문에 가비지 콜렉터가 나타나서 그 즉시 수거해 갈 것이다. (실제로는, 이 부분은 컴파일 타임에 없어질 것이다.) 레퍼런스에 이름을 붙여보자.

let my_ref = ref 0;;

이 레퍼런스는 현재 정수 0을 저장하고 있다. 다른 값을 그 안에 넣어보자(대입).

my_ref := 100;;

이제 레퍼런스가 어떤 값을 가지고 있는지 확인해보자.

# !my_ref;;
- : int = 100

이와 같이 연산자 :=는 레퍼런스에 값을 대입하기 위해 사용되고, 연산자 !는 내용을 꺼내오는 디레퍼런스(dereference)에 사용된다. 여기 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나 Java등에서는 사이드 이펙트가 빈번하게 사용되지만, 함수형 언어인 Ocaml 에서는 되도록이면 레퍼런스나 unit 타입의 함수는 사용하지 않는것이 좋다. 왜냐하면 프로그램의 거의 대부분의 에러는 이 사이드 이펙트에 있기 때문이다.)

중첩 함수(nested functions)

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;;

이 코드가 무엇을 하는지는 걱정하지 말기 바란다- 여기에는 아직 튜토리얼에서 이야기하지 않는 많은 개념이 포함되어 있다. 대신 중앙에 위치한 unit 인자를 받는 중첩 함수 loop에 집중하기 바란다. 여러분은 read_whole_channel 함수 내에서 이 함수 내부에 정의된 loop()를 부를 수 있다. 중첩 함수는 메인 함수에 정의된 변수에 접근할 수 있다. (여기서는 loop에서 지역 이름인 buf에 접근 가능하다.)

중첩 함수의 모양새는 이름 붙은 지역 표현식과 동일하다: let name arguments = function-definition in

함수 정의를 할 때는 보통 위와 같이 새로운 줄에서 들여쓰기를 한다. 함수가 (위의 예제처럼) 재귀적일 때는, let 대신에 let rec를 써야함을 명심하자.

모듈(module)과 open

OCaml에는 여러 재미있는 모듈(유용한 코드의 라이브러리)이 많이 존재한다. 예를 들어, 그림을 그리는 모듈, GUI 위젯 집합과 인터페이싱하는 모듈, 큰 숫자와 데이터 구조를 다루는 모듈, POSIX 시스템 콜을 하는 모듈 등이 있다. 이들 라이브러리는 (유닉스에서는)/usr/lib/ocaml/VERSION/에 위치한다. 예제를 위해서 우리는 매우 간단한 모듈인 Graphics에 집중하도록 하겠다.

Graphics 모듈은 5개의 파일로 설치되어 있다.

/usr/lib/ocaml/3.08/graphics.a
/usr/lib/ocaml/3.08/graphics.cma
/usr/lib/ocaml/3.08/graphics.cmi
/usr/lib/ocaml/3.08/graphics.cmxa
/usr/lib/ocaml/3.08/graphics.mli

우선 graphics.mli 파일을 살펴보자. 이 파일은 텍스트 형식이며, 여러분들은 바로 읽을 수 있다. Graphics.mli가 아닌 graphics.mli인 점에 주목하자. OCaml에서 모듈 이름을 얻으려면 항상 파일 이름의 첫 글자를 대문자로 바꿔주면 된다. 이 부분은 왜 그런지 알기 전까지는 매우 혼란스러울 수 있다.

여러분이 Graphics 내의 함수를 사용하고 싶다면, 2가지 방법이 있다. 프로그램 시작 시에 open Graphics;; 선언을 넣어주는 방법이 있고, 모든 함수 호출 에 Graphics.open_graph와 같이 접두어를 붙여주는 방법이 있다. open은 자바의 import문과 조금 유사하고, 펄의 use 구문과 매우 흡사하다.

[윈도 사용자의 경우: 윈도에서 이 예제가 상호작용하려면, 커스컴 탑레벨을 만들 필요가 있다. 명령행에서 ocamlmktop -o ocaml-graphics graphics.cma라고 명령을 주기 바란다.]

몇 개의 예를 살펴보면 확실히 이해가 될 것이다. (두 예제는 다른 그림을 그린다. 시도해보자.) 첫 번째 예제는 open_graph를 호출하고, 두 번째 예제는 Graphics.open_graph를 호출한다.

(* 이 예제를 컴파일하려면: ocamlc graphics.cma grtest1.ml -o grtest1 *)

open Graphics;;

open_graph " 640x480";;
for i = 12 downto 1 do
  let radius = i * 20 in
  set_color (if (i mod 2) = 0 then red else yellow);
  fill_circle 320 240 radius
done;;
read_line ();;
(* 이 예제를 컴파일하려면: ocamlc graphics.cma grtest2.ml -o grtest2 *)

Random.self_init ();;
Graphics.open_graph " 640x480";;

let rec iterate r x_init i =
        if i = 1 then x_init
        else
                let x = iterate r x_init (i-1) in
                r *. x *. (1.0 -. x);;

for x = 0 to 639 do
        let r = 4.0 *. (float_of_int x) /. 640.0 in
        for i = 0 to 39 do
                let x_init = Random.float 1.0 in
                let x_final = iterate r x_init 500 in
                let y = int_of_float (x_final *. 480.) in
                Graphics.plot x y
        done
done;;

read_line ();;

두 예제 모두 우리가 아직 배우지 않은 기능인 명령형 스타일의 for 루프, if-then-else와 재귀 등을 사용하고 있다. 이 기능에 대해서는 나중에 이야기하겠다. 그럼에도 불구하고 여러분은 이 예제를 통해 (1) 프로그램이 어떻게 동작하는지 (2) 타입 추론이 버그를 제거하는 데 어떻게 도움을 주는지 살펴보아야 한다.

Pervasives 모듈

여러분이 절대 "open"할 필요가 없는 모듈이 하나 있는데, 바로 Pervasives 모듈이다. (지금 /usr/lib/ocaml/3.08/pervasives.mli 코드를 읽어보기 바란다.) Pervasives 모듈의 모든 심볼은 자동으로 모든 OCaml 프로그램에 포함된다.

(역자주 : 윈도우즈 계열의 OS 를 쓰는 사람은 C:\Program Files\Objective Caml\lib\pervasives.mli 에 위치한다. Pervasives 의 뜻은 '스며들다' 라는 의미다. 마치 Ocaml에 스며드는거 같지 않는가?)

모듈 이름 바꾸기

(역자주 : 엄밀히 말하면 이름을 바꾸는것이 아니고, 새 이름을 하나더 다는 것이다.)

Graphics의 심볼을 사용하고 싶은데, 모두를 임포트하고 싶지도 않고 그렇다고 매번 Graphics를 치기도 귀찮다면 어떻게 해야할까? 다음 방법으로 모듈의 이름을 바꾸면 된다.

module Gr = Graphics;;

Gr.open_graph " 640x480";;
Gr.fill_circle 320 240 240;;
read_line ();;

실제로 이 방법은 중첩된 모듈을 임포트할 때 매번 전체 패스를 다 치지 않아도 된다는 점에서 무척 유용하다. (모듈은 다른 모듈에 중첩될 수 있다.)

;;; 사용 및 생략

(역자주 : ;; 의 의미는 앞에서 정의한 함수나 변수를 실행하라는 뜻이다. 인터프리터에서만 사용하고, 에디터에서 코드를 작성할때는 사용하지 않도록 하자! 아래의 룰이 있지만 복잡하고, 프로그램의 결과에 영향을 줄 수 있다.)

(역자주 : ; 의 의미는 표현식을 순차적으로 실행할 때 구분자인데, C언어의 문장의 끝을 구분하는 것과는 쓰임이 다르다. )

지금부터 우리는 매우 중요한 문제를 살펴보려 한다. 언제 ;;;를 사용해야 하고, 언제 아무 것도 쓰지 않아야 할까? 이 문제는 이해하기 전까지는 무척 헷갈리는 문제이며, 필자도 이 문제로 오랜 시간 골머리를 앓았다.

규칙 #1은 탑 레벨 코드를 분리하는데 ;;를 사용하며, 함수 정의 내에서나 다른 구문에서는 절대 사용하지 말아야 한다는 것이다.

앞서 나온 두 번째 그래픽스 코드를 살펴보자.

Random.self_init ();;
Graphics.open_graph " 640x480";;

let rec iterate r x_init i =
        if i = 1 then x_init
        else
                let x = iterate r x_init (i-1) in
                r *. x *. (1.0 -. x);;

여기에 2개의 탑 레벨 구문과 하나의 함수 정의(iterate라는 함수)가 있다. 각각은 ;;로 끝나고 있다.

규칙 #2는 종종 ;;를 생략할 수 있다는 것이다. 초보자인 여러분은 이 문제에 대해 고민할 필요가 없다. 여러분은 규칙 #1에서 이야기한 것처럼 반드시 ;;를 삽입해야 한다. 하지만 다른 사람의 코드를 읽을 일이 많을텐데, 종종 ;;가 생략될 수 있다는 사실 또한 알아야 한다. 생략이 가능한 지점은,

다음은 생략 가능한 지점에 ;;를 모두 생략한 동작하는 코드이다.

open Random                   (* ;; *)
open Graphics;;

self_init ();;
open_graph " 640x480"         (* ;; *)

let rec iterate r x_init i =
        if i = 1 then x_init
        else
                let x = iterate r x_init (i-1) in
                r *. x *. (1.0 -. x);;

for x = 0 to 639 do
        let r = 4.0 *. (float_of_int x) /. 640.0 in
        for i = 0 to 39 do
                let x_init = Random.float 1.0 in
                let x_final = iterate r x_init 500 in
                let y = int_of_float (x_final *. 480.) in
                Graphics.plot x y
        done
done;;

read_line ()                  (* ;; *)

규칙 #3과 #4는 한 개짜리 ;를 말한다. 이는 ;;와는 완전히 다르다. 세미콜론 하나 ;시퀀스 포인트(sequence point)라 알려져 있는데, C, C++, 자바, 펄의 세미클론과 동일한 목적을 가지고 있다.이는 "이 지점 이전의 일을 먼저 하고, 앞선 일이 끝나면 이 지점 이후의 일을 하라"는 뜻이다. 여러분은 이 사실을 몰랐을 것이다.

규칙 #3: let ... in을 구문으로 생각하고, 그 뒤에 절대 단일 ;를 쓰지마라.

규칙 #4: 코드 블록 내의 다른 모든 구문에는 마지막 구문을 제외하고 ;를 사용하라.

앞서 나온 내부 for 루프는 좋은 예제이다. 여기서 ;가 절대 쓰이지 않음을 주목하자.

        for i = 0 to 39 do
                let x_init = Random.float 1.0 in
                let x_final = iterate r x_init 500 in
                let y = int_of_float (x_final *. 480.) in
                Graphics.plot x y
        done

위 코드에서 ;를 넣을 수 있는 유일한 지점은 Graphics.plot x y인데, 이는 블록 내의 마지막 구문이므로 규칙 #4에 의해서 세미콜론을 넣지 않는다.

";"에 관해 유의사항 브라이언 허트(Brian Hurt) ";"에 대해 필자의 의견을 고쳐주었다.

;+와 마찬가지로 연산자이다. 정확히 +와 같은 것은 아니지만 개념적으로는 동일하다. +int -> int -> int 타입인데, 두 개의 정수 인자를 받아서 정수(합)를 리턴한다. ;의 타입은 unit -> 'b -> 'b인데, 두 개의 값을 받아서 단순히 두 번째 값을 리턴한다. C의 ,(콤마) 연산자와 유사하다. 여러분은 a + b + c + d라고 쓸 수 있는 것과 마찬가지로 a ; b ; c ; d라고 쓸 수 있다.

이 부분은 제대로 언급된 적이 없는 "정신적인 도약" 중에 하나인데, OCaml에서 모든 것은 표현식이라는 점이다. if/then/else도 표현식이고, a ; b도 표현식이다. match foo with ... 또한 표현식이다. 다음 코드는 완벽히 합법적이다. (모두 같은 일을 한다.)

let f x b y = if b then x+y else x+0

let f x b y = x + (if b then y else 0)

let f x b y = x + (match b with true -> y | false -> 0)

let f x b y = x + (let g z = function true -> z | false -> 0 in g y b)

let f x b y = x + (let _ = y + 3 in (); if b then y else 0)

특히 마지막 것에 주목하자. 여기서 ;는 두 구문을 합치기 위한 연산자로 사용되었다. OCaml의 모든 함수는 다음과 같이 표현될 수 있다.

let name [parameters] = expression

OCaml에서 표현식은 C보다 약간 광범위할 뿐이다. 사실, C는 구문의 개념이 있지만, C의 모든 구문은 OCaml에서는 (;로 합쳐진) 표현식일 뿐이다.

;+와 다른 것은 +를 함수처럼 참조할 수 있다는 점이다. 예를 들어 정수 리스트의 합을 계산하는 sum_list 함수는 다음과 같이 정의할 수 있다.

let sum_list = List.fold_left ( + ) 0

종합하기: 실제 코드

이 절에서는 lablgtk 1.2을 사용한 실제 코드의 일부를 보여주려 한다. (Lablgtk는 Unix Gtk 위젯 라이브러리의 OCaml 인터페이스이다.) 경고문: 이 코드는 우리가 아직 이야기하지 않은 기능을 많이 포함하고 있다. 자세한 내용을 보지 말고, 코드의 전반적인 생김새와 코드 작성자가 어디에 ;;;를 사용했는지, 어디에 open를 사용했는지, 코드 들여쓰기를 어떻게 했는지, 지역/전역 이름 붙은 표현식을 어떻게 사용했는지 등을 유의하여 보자.

... 하지만 몇 가지 힌트를 통해 여러분이 완전히 헤매지 않도록 돕도록 하겠다.

첫 번째 코드 예제: 프로그래머는 몇 개의 표준 라이브러리를 열고 (다음 키워드가 각각 openlet이므로 ;;를 생략한다.) 그리고 file_dialog라는 함수를 생성한다. 이 함수에서 두 줄의 let sel = ... in 구문을 이용해 이름 붙은 표현식 sel를 정의한다. 그리고 sel에 몇 개의 메쏘드를 호출한다.

(* 첫 번째 코드 예제 *)

open StdLabels
open GMain

let file_dialog ~title ~callback ?filename () =
  let sel =
    GWindow.file_selection ~title ~modal:true ?filename () in
  sel#cancel_button#connect#clicked ~callback:sel#destroy;
  sel#ok_button#connect#clicked ~callback:do_ok;
  sel#show ()

두 번째 코드 예제: 탑 레벨의 긴 전역 이름 리스트. 작성자가 규칙 #2를 이용해 ;;를 모두 제거했음을 유의하자.

(* 두 번째 코드 예제 *)

let window = GWindow.window ~width:500 ~height:300 ~title:"editor" ()
let vbox = GPack.vbox ~packing:window#add ()

let menubar = GMenu.menu_bar ~packing:vbox#pack ()
let factory = new GMenu.factory menubar
let accel_group = factory#accel_group
let file_menu = factory#add_submenu "File"
let edit_menu = factory#add_submenu "Edit"

let hbox = GPack.hbox ~packing:vbox#add ()
let editor = new editor ~packing:hbox#add ()
let scrollbar = GRange.scrollbar `VERTICAL ~packing:hbox#pack ()

세 번째 코드 예제: 작성자는 GdkKeysyms 모듈에서 모든 심볼을 임포트했다. 여기에는 일반적이지 않은 let 바인딩이 있다. let _ = expression는 "표현식의 값을 계산한 후에(일어날 수 있는 부작용(side-effect)를 모두 고려해서), 결과값을 버려라"라는 뜻이다. 이 경우, "표현식의 값을 계산하라"는 부분은 윈도를 스크린에 띄우고 전체 어플리케이션을 실행시키는 Gtk의 메인 루프 Main.main ()를 수행하라는 뜻이다. Main.main ()의 "결과값"은 중요하지 않다. 확인해보진 않았지만 아마 unit 리턴 값일 것이다. 이 값은 어플리케이션이 끝날 때까지 리턴되지 않는다.

이 코드 예제에서 실제로 일련의 프로시저 명령을 어떻게 표현했는지 살펴보기 바란다. 이는 실제로는 고전적인 명령형 프로그램이다.

(* 세 번째 코드 예제 *)

open GdkKeysyms

let _ =
  window#connect#destroy ~callback:Main.quit;
  let factory = new GMenu.factory file_menu ~accel_group in
  factory#add_item "Open..." ~key:_O ~callback:editor#open_file;
  factory#add_item "Save" ~key:_S ~callback:editor#save_file;
  factory#add_item "Save as..." ~callback:editor#save_dialog;
  factory#add_separator ();
  factory#add_item "Quit" ~key:_Q ~callback:window#destroy;
  let factory = new GMenu.factory edit_menu ~accel_group in
  factory#add_item "Copy" ~key:_C ~callback:editor#text#copy_clipboard;
  factory#add_item "Cut" ~key:_X ~callback:editor#text#cut_clipboard;
  factory#add_item "Paste" ~key:_V ~callback:editor#text#paste_clipboard;
  factory#add_separator ();
  factory#add_check_item "Word wrap" ~active:false
    ~callback:editor#text#set_word_wrap;
  factory#add_check_item "Read only" ~active:false
    ~callback:(fun b -> editor#text#set_editable (not b));
  window#add_accel_group accel_group;
  editor#text#event#connect#button_press
    ~callback:(fun ev ->
      let button = GdkEvent.Button.button ev in
      if button = 3 then begin
        file_menu#popup ~button ~time:(GdkEvent.Button.time ev); true
      end else false);
  editor#text#set_vadjustment scrollbar#adjustment;
  window#show ();
  Main.main ()

Discuss this page