
;; и ;average на Си и добавим в неё локальные переменные (сравните её с примером в прошлой главе).
double average (double a, double b)
{
double sum = a + b;
return sum / 2;
}
let average a b = let sum = a +. b in sum /. 2.0;;
let name = expression in используется для определения локального именованного выражения и name может быть использовано в дальнейшем вместо expression вплоть до ;;, который означает окончание локального блока кода. Обратите внимание, мы даже не использовали отступ после объявления in. Просто воспринимайте let ... in так, как будто это один оператор.sum в коде на Си использует память в стеке. Вы можете в дальнейшем присвоить sum любое значение, какое захотите, или даже получить адрес памяти, где хранится значение. Для версии на OCaml это не так. В ней sum — всего лишь краткое название выражения a +. b. Нет никакой возможности присвоить что-либо sum. (Чуть позже мы покажем, как создавать переменные, у которых можно менять значение).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 () =
(* code omitted *)
factory#add_item "Cut" ~key:_X ~callback: html#cut
;;
html — виджет для редактирования HTML (объект библиотеки lablgtk), созданный в начале программы выражением let html =. Потом, далее, он используется в нескольких функциях.html в фрагменте кода выше не должно восприниматься как настоящая глобальная переменная, как в Си или других императивных языках. Нет никакого выделения памяти для «хранения» «указателя html». Невозможно присвоить html что-либо, например, переопределить его как как указатель на другой виджет. В следующей секции мы обсудим ссылки, которые являются настоящими переменными.let ..., не важно где, на верхнем уровне (глобально) или внутри функций (локально), часто называется let-binding (связыванием с использованием let).int в OCaml:ref 0;;
let my_ref = ref 0;;
my_ref := 100;;
# !my_ref;; - : int = 100
:= используется для присвоения ссылок, а оператор ! разыменовывает ссылки, возвращая содержимое. Вот грубое, но эффективное сравнение с Си/Си++:| OCaml | C/C++ |
|---|---|
let my_ref = ref 0;; my_ref := 100;; !my_ref |
int a = 0; int *my_ptr = &a; *my_ptr = 100; *my_ptr |
let name=expression in для именования локальных выражений в ваших функциях.«Вложенная функция» — это функция, определённая внутри другой функции (Вложенные функции не поддерживаются для GNU C++). Имена вложенных функций являются локальными для блока, в котором они были определены. Например, вот определение вложенной функции 'square', которая вызывается дважды:
foo (double a, double b) { double square (double z) { return z * z; } return square (a) + square (b); }
Вложенная функция имеет доступ ко всем функциям внешней функции, которые видимы на момент определения функции. Это так называемая «область лексической видимости». Вот пример вложенной функции, которая наследует переменную, называющуюся 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) /* ... */ }
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, которая принимает аргумент с типом unit. Вы можете вызвать loop () из функции read_whole_channel, а за пределами функции она не определена. Вложенная функция имеет доступ к переменным, определённым в главной функции (так loop получает доступ к локальным переменным buf и chan).let name arguments = function-defenition in.let rec вместо let, если функция рекурсивна (как в примере выше).openGraphics.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, то есть два пути. Либо в начале нашей программы мы пишем декларацию open Graphics;;, либо мы дополняем все вызовы соответствующих функций префиксом: Graphics.open_graph.open. open слегка напоминает функцию import в Java, и чуть больше (напоминает) выражение use в Перле.open_graph, второй вызывает Graphics.open_graph. [прим. пер.: В начале статьи скриншоты того, что делают программы].(* To compile this example: 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 ();;
(* To compile this example: 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) как выведение типов позволяет отлавливать ошибки.PervasivesPervasives (находится в /usr/lib/ocaml/3.08/pervasives.mli [прим. пер.: у меня /usr/lib/ocaml/pervasives.mli]). Все символы из модуля Pervasives автоматически импортируются во все программы на OCaml.Graphics, но не хотите импортировать их все, и вам лениво печатать Graphics каждый раз? Просто переименуйте модуль, используя этот приём:module Gr = Graphics;; Gr.open_graph " 640x480";; Gr.fill_circle 320 240 240;; read_line ();;
;; и ;;;, когда следует использовать ;, а когда следует пропускать оба варианта? Это хитрый вопрос остаётся до тех пор, пока вы в нём хорошо не разберётесь. Беспокоил он длительное время и автора, пока пока тот изучал OCaml.;; для отделения утверждений в верхнем уровне вашего кода (top-level) и никогда внутри определения функций или иного рода утверждений.
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);;
iterate. Каждое из них завершается ;;.;;. Как неофит, вам не следует особо задумываться об этом правиле и всегда писать ;;, как предписывает правило №1. Но если вы будете читать чужой код, вы будете иногда встречаться с отсутствием ;;. Места, в которых можно опускать ;;:let;open;type;;; опущено всюду, где возможно:
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 () (* ;; *)
;. Они полностью отличаются от правил для ;;. Единичная точка с запятой (;) называется точкой последовательности [прим. пер.: могу ошибаться с переводом sequence point], которая играет ровно такую же роль, как одиночная точка с запятой в языках Си, Си++, Java или Перл. Она значит «сделай всё после этого места когда сделаешь всё до него». Спорим, вы не знали этого.let ... 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
; — это строчка Graphics.plot x y, но так как это последняя строчка блока, то согласно правилу №4, её ставить не надо.;— такой же оператор, как, например, оператор сложения (+). Ну, не совсем как +, но по сути — именно так. + имеет типint -> int -> int— он берёт два целых и возвращает целое (их сумму).;имеет типunit -> 'b -> 'b— он берёт два зачения и просто возвращает второе. В отличие от запятой в Си. Вы можете написать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 несколько шире, чем в Си. На самом деле, Си имеет концепцию „утверждений“, но все утверждения в Си — всего лишь выражения в OCaml (объединённые;).
Единственным различием между;и+является возможность ссылки на+, как на функцию. Например, я могу определить функциюsum_listдля суммирования списков целых как:
let sum_list = List.fold_left ( + ) 0
;;, где они использовали ;, где они использовали open, как они отбивали текст, как они использовали локальные и глобальные выражения.?foo и ~foo — способы указывать опциональные и именованные аргументы функций в OCaml. Прямой параллели с Си-подобными языками провести не удастся, но в Perl, Python и Smalltalk есть эта концепция. Вы можете называть аргументы при вызове функции, опускать некоторые из них и передавать оставшиеся в том порядке, как вам удобно.foo#bar — вызов метода (вызов метода, называющегося bar в отношении объекта, называющегося foo). Это подобно foo->bar, foo.bar, $foo->bar (Си++, Java, Perl).;;, потому что следующие ключевые слова — это open или let). Так же он создаёт функцию, называющуюся file_dialog. Внутри этой функции он определяет именованное выражение с названием sel используя двустрочное утверждение let sel = .. in. Затем он вызывает несколько методов в отношении sel.
(* First snippet *)
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.(* Second snippet *) 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 ()
GdkKesyms. Потом следует необычный let-binding. let _ = expression означает „рассчитать значение выражения (включая выполнение всех побочных эффектов), но выкинуть результат“. В данном случае „рассчитать значение выражения“ означает выполнение Main.main (), которая является основным циклом GTK, побочным эффектом которого является появление окна на экране и выполнения всего приложения. „Результат“ вызова Main.main () незначим. Вероятнее всего это unit, но я не проверял — и оно точно не возвращается до завершения приложения.
(* Third snippet *)
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 ()
Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
комментарии (24)