21 ноября 2010 в 06:29

Введение в OCaml: The Basics [1] перевод

(предисловие от переводчика: сел учить окамл, обнаружил, что отсутствует перевод на русский язык руководства для начинающих. Восполняю этот пробел).

Основы


Комментарии


Комментарии в OCaml обозначаются символами (* и *), примерно так:
(* Это однострочный комментарий *)

(* Это комментарий
   на несколько
   строк.
*)

Другими словами, комментарии в OCaml очень похожи на комментарии в Си (/* ... */).

В настоящий момент нет однострочных комментариев (как #... в Перле или // ... в C99/C++/Java). Когда-то обсуждалась возможность использовать ## ..., и я весьма рекомендую окамловским товарищам в будущем добавить эту возможность (однако, хорошие редакторы открывают возможность использования однострочных комментариев даже сейчас).

Комментарии в OCaml вложенные, это позволяет очень просто комментировать куски кода с комментариями:
(* This code is broken ...

(* Primality test. *)
let is_prime n =
(* note to self: ask about this on the mailing lists *) XXX;;

*)

Вызов функций


Допустим, вы написали функцию, назовём её repeated, которая берёт исходную строку s, число n и возвращает новую строку, состоящую из n раз повторённой строки s.

В большинстве С-подобных языков вызов функции будет выглядеть так:
repeated ("hello", 3) /* this is C code */

Это означает «вызвать функцию repeated с двумя аргументами, первый аргумент — строка hello, второй аргумент — число 3».

Подобно остальным функциональным языкам программирования, в OCaml, запись вызовов функций и использование скобок существенно отличается, что приводит к множеству ошибок. Вот пример того же самого вызова, записанного на OCaml: repeated "hello" 3 (* this is OCaml code *).

Обратите внимание — нет скобок, нет запятых между аргументами.

Выражение repeated ("hello", 3) с точки зрения OCaml имеет смысл. Оно означает «вызвать функцию repeated с ОДНИМ аргументом, являющимся структурой „пара“, состоящей из двух элементов». Разумеется, это приведёт к ошибке, потому что функция repeated ожидает не один, а два аргумента, и первый аргумент должен быть строкой, а не парой. Но, не будем пока особо вдаваться в подробности о парах (кортежах). Вместо этого, просто запомните: использование скобок и запятых при передаче аргументов в функцию — ошибка.

Рассмотрим другую функцию — prompt_string, которая принимает строку с текстом приглашения и возвращает ввод пользователя. Мы хотим передать результат работы этой функции в функцию repeated. Вот версии на Си и OCaml:

/* C code: */
repeated (prompt_string ("Name please: "), 3)
(* OCaml code: *)
repeated (prompt_string "Name please: ") 3


Взгляните тщательнее на расстановку скобок и отсутствующую запятую. В версии на OCaml в скобки взят первый аргумент функции repeated, который является результатом вызова другой функции. Общее правило: «скобки вокруг вызова функции, а не вокруг аргументов функции». Вот ещё несколько примеров:

f 5 (g "hello") 3    (* у f три аргумента, у g - один *)
f (g 3 4)            (* у f один аргумент, у g - два *)

# repeated ("hello", 3);;     (* OCaml выдаст ошибку *)
This expression has type string * int but is here used with type string

Определение функций


Допустим, вы все знаете, как определять функции (или статические методы для Java) в привычных языках. Но как мы делаем это в OCaml?

Синтаксис OCaml изящен и лаконичен. Вот функция, которая принимает два аргумента с плавающей запятой и вычисляет среднее:

let average a b =
  (a +. b) /. 2.0;;


Задайте это на верхнем уровне (toplevel) OCaml. (для этого, в unix, просто наберите команду ocaml). [Прим. пер. для ubuntu/debian sudo aptitude install ocaml, для suse/centos/fedora — sudo yum install ocaml]. Вы увидите:
# let average a b =
  (a +. b) /. 2.0;;
val average : float -> float -> float = <fun>


Если вы присмотритесь к определению функции и тому, что написал OCaml, у вас возникнет несколько вопросов:
  • Что делают дополнительные точки с запятой в коде?
  • Что значит это всё float -> float -> float ?

Мы ответим на этот вопрос в следующих секциях, а пока я хотел бы определить такую же функцию на Си (на Java она бы выглядела похоже), и, надеюсь, вызвать ещё несколько вопросов. Вот версия на Си той же самой функции average:

double
average (double a, double b)
{
  return (a + b) / 2;
}


Сравните с более компактной версией на OCaml. Надеюсь, у вас возникли вопросы:
  • Почему мы не задали типы переменных a и b в версии на OCaml? Как OCaml определил типы? (и, вообще, OCaml знает типы, или он полностью динамически типизированный язык?)
  • В Си число 2 неявно приведено к типу double, может ли OCaml делать так же?
  • Как в OCaml записывается аналог оператора return?

ОК, вот некоторые ответы:
  • OCaml — язык со строгой статической типизацией (другими словами, никаких динамических приведений, подобных приведениями между int, float и string в Перле)
  • OCaml использует выведение типов для определения типов, так что вам не приходится делать это руками. Если вы вводите код на верхнем уровне OCaml, как в примере выше, то OCaml сообщает выведение типов в вашей функции
  • OCaml не осуществляет никаких неявных приведений типов. Если вы хотите число с плавающей запятой (float), то вы должны в явном виде писать 2.0, потому что 2 — это целое [прим. пер.: В английском дробная часть отделяется от целой точкой, а тип называется, дословно, «число с плавающей точкой», так что в OCaml для отделения целой и дробной частей используется точка]. OCaml не осуществляет автоматического конвертирования между типами int, float, string или любыми другими.
  • Как побочный эффект выведения типов в OCaml, функции (включая операторы) не могут быть перегружены. OCaml определяет + как операцию сложения целых чисел. Для сложения чисел с плавающей запятой используйте +. (внимание на точку после знака плюса). Аналогично, используются -., *., /. для прочих операций с плавающей запятой.
  • У OCaml нет оператора return — последнее выражение в функции используется как значение функции автоматически.

Мы обсудим подробнее всё это в последующих секциях и главах.

Основные типы


Тип Диапазон значений
int 31-битное знаковое целое на 32-битных системах и 63-битное знаковое целое на системах с 64-битным процессором
float Число с плавающей запятой двойной точности (IEEE), эквивалентно double в Си
bool Логический тип, значения true/false
char 8-битный символ
string Строка


OCaml использует один из битов типа int для хранения данных для автоматического управления памятью (сборки мусора). Вот почему размерность int 31 бит, а не 32 бита (63 бита для 64-битных систем). В обычном использовании это не проблема, за исключением нескольких специфичных случаев. Например, если вы считаете что-то в цикле, OCaml ограничивает количество итераций 1 миллиардом вместо 2. Это не проблема, потому что если вы считаете что-то близко к лимиту int в любом языке, вы должны использовать специальные модули для работы с большими числами (Nat и Big_int модули в OCaml). Однако, для обработки 32-битных значений (например, криптокод или код сетевого стека) OCaml предоставляет тип nativeint, соответствующий битности целого на платформе.

У OCaml нет основного типа, соответствующего беззнаковому целому, однако, вы можете получить его, используя nativeint. Насколько я знаю, OCaml не имеет поддержки чисел с плавающей запятой одинарной точности.

В OCaml тип char, использующийся для представления символов текста. К сожалению, тип char не поддерживает Unicode, ни в виде многобайтных кодировок, ни в виде UTF-8. Это серьёзный недостаток OCaml, который должен быть исправлен, а пока существуют обширные библиотеки для поддержки unicode, которые должны помочь.

Строки не являются просто последовательностью байтов. В них используется свой собственный, более эффективный метод хранения.

Тип unit является некоторым подобием типа void в Си, но мы поговорим о нём позже.

Явная типизация против неявной



В Си-подобных языках целое превращается в число с плавающей запятой при некоторых обстоятельствах. Например, если вы напишите 1 + 2.5, то первый аргумент (целое) будет приведён к плавающей запятой, и результат так же будет плавающей запятой. Подобного можно добиться, записав явно ((double)1)+2.5.

OCaml никогда не делает неявного приведения типов В OCaml 1 + 2.5 — ошибка типа. Оператор сложения + требует двух целых аргументов и сообщает об ошибке, если один из аргументов является числом с плавающей запятой:

# 1 + 2.5;;
      ^^^
This expression has type float but is here used with type int


(В специфичном языке «перевод с французского» сообщение об ошибке означает «ты положил плавающую запятую тут, а я ожидал целое») [прим. пер.: OCaml разрабатывался французами и автор подшучивает над неудачным переводом сообщений об ошибках с французского на английский].

Для сложения двух чисел с плавающей запятой используется другой оператор, +. (обратите внимание на точку).

OCaml не приводит целые к плавающей запятой, так что вот это тоже ошибка:
# 1 +. 2.5;;
  ^
This expression has type int but is here used with type float


Теперь OCaml жалуется на первый аргумент.

А что делать, если нужно сложить целое число и число с плавающей запятой? (Пусть они сохранены в переменных i и f). В OCaml необходимо осуществить прямое приведение типов:

(float_of_int i) +. f;;


float_of_int — функция, принимающая целое и возвращающая число с плавающей запятой. Есть целая пачка таких функций, выполняющая подобные действия, называющихся примерно так: int_of_float, char_of_int, int_of_char, string_of_int. По большей части они делают то, что от них ожидают.

Так как конвертация int в float весьма частая, у float_of_int есть короткий псевдоним. Пример выше может быть записан как
float i +. f;;

(Обратите внимание, в отличие от Си, в OCaml и функция, и тип могут иметь одинаковое имя).

Что лучше — явное или неявное приведение?


Вы можете подумать, что явное приведение уродливо, что это нудное занятие. В чём-то вы правы, но есть как минимум два аргумента в пользу явного приведения. Во-первых, OCaml использует явное приведение типов для возможности выведения типов (см. ниже), а выведение типов — это настолько замечательная и экономящая время функция, что она очевидно перевешивает лишние нажатия кнопок при явной типизации. Во-вторых, если вы хоть раз занимались отладкой программ на Си, вы знаете, что (а) неявная типизация вызывает трудные для нахождения ошибки, и (б) большую часть времени вы сидите и пытаетесь понять, где сработала неявная типизация. Требование явной типизации помогает в отладке. Третье, некоторые приведения типов (особенно, целое <-> плавающая запятая) в реальности весьма дорогие операции. Вы делаете себе медвежью услугу, скрывая их в неявной типизации.

Обычные и рекурсивные функции


В отличие от Си-подобных языков, функции не допускают рекурсии если это явно не указано с помощью выражения let rec вместо обычного let Вот пример рекурсивной функции:

let rec range a b =
  if a > b then []
  else a :: range (a+1) b
  ;;

Обратите внимание — range вызывает саму себя.

Единственным различием между let и let rec является область видимости имени функции. Если бы функция из примера выше была бы определена с использованием просто let, то вызов функции range осуществил бы поиск существующей (ранее определённой) функции, называющейся range, а не прямо-сейчас-определяемой функции. Использование let (без rec) позволит вам переопределять значение в терминал предыдущего определения. Например:
let positive_sum a b = 
    let a = max a 0
    and b = max b 0 in
    a + b


Переопределение прячет предыдущую «привязку» a и b из определения функции. В некоторых ситуациях программисты предпочитают этот подход использованию новых переменных (let a_pos = max a 0) так как это делает старые привязки недоступными, оставляя доступными только новейшие значения a и b.

Определение функций через let rec не даёт каких-либо изменений в производительности по сравнению с let, так что если вам нравится, вы можете всегда использовать форму let rec, чтобы получить аналогичное Си-подобным языкам поведение.

Типизация значений функций


Благодаря выведению типов вам редко придётся явно указывать тип возвращаемого функцией значения. Однако, OCaml часто выводит что он думает о типе возвращаемого значения ваших функций, так что вы должны знать синтаксис для подобных записей. Для функции f, которая принимает аргументы arg1, arg2,… argn, и возвращает значение rettype компилятор выведет:
f : arg1 -> arg2 -> ... -> argn -> rettype

Синтаксис с использованием стрелки выглядит непривычно, но потом мы подойдём к так называемым производным функциям (currying), вы поймёте, почему он такой. Пока приведём несколько примеров.

Наша функция repeated принимает строку и целое, возвращает строку. Её тип описывается так:
repeated : string -> int -> string

Наша функция average, принимающая два числа с плавающей запятой и возвращающая одно число с плавающей запятой, описывается так:
average : float -> float -> float

Стандартная функция OCaml int_of_char приводит:
int_of_char : char -> int


Если функция ничего возвращает (void для Си и Java), то мы записываем, что она возвращает тип unit. Например, вот так выглядит на OCaml аналог функции fputs:
output_char : out_channel -> char -> unit

Полиморфные функции


Теперь чуть больше странного. Как насчёт функции, которая принимает что угодно в качестве аргумента? Вот пример ненормальной функции, которая берёт один аргумент, но игнорирует его и всегда возвращает число 3:
let give_me_a_three x = 3;;

Какой тип у этой функции? В OCaml используется специальный заместитель, означающий «что вашей душе угодно». Это одиночная кавычка с последующей буквой. Тип функции выше записывается как:
give_me_a_three : 'a -> int

где 'a в реальности означает «любой тип». Вы, например, можете вызывать эту функцию как give_me_a_three "foo" или give_me_a_three 2.0. Оба варианта будут одинаково правильными с точки зрения OCaml.

Пока что не очень понятно, почему полиморфные функции полезны, но на самом деле они очень полезны и очень распространены; мы обсудим их позже. (Подсказка: полифорфизм — это что-то вроде шаблонов в C++ или generic в Java).

Выведение типов


Темой этого учебника является мысль, о том, что функциональные языки содержат в себе Много Клёвых Фич и OCaml — это язык, который имеет все Клёвые Фичи собранными в одном месте, делая его очень полезным на практике для реальных программистов. Но вот что странно — большинство этих полезных возможностей не имеют никакого отношения к «функциональному программированию». На самом деле, я подошёл к первой Клёвой Фиче и я всё ещё не сказал ни слова про то, почему функциональное программирование называется «функциональным». В любом случае, вот первая Клёвая Фича: выведение типов.

Просто говоря, вам не нужно декларировать типы для ваших функций и переменных, потому что OCaml сделает это за вас.

В дополнение, OCaml сделает все проверки типов для вас, даже между несколькими файлами.

Но OCaml — практический язык, и ради этого он содержит бэкдор в систему управления типами, позволяя вам обойти проверку типов в тех редких случаях, когда это имеет смысл. Вероятнее всего, обход проверки типов нужен только настоящим гуру OCaml.

Вернёмся к функции average, которая была введена на верхнем уровне OCaml.
# let average a b =
  (a +. b) /. 2.0;;
val average : float -> float -> float = <fun>


Mirabile dictu! OCaml всё сделал сам, определил, что функция принимает два float и возвращает float.

Как? Сперва видно, что a и b использовались в выражении (a + . b). Поскольку известно, что функция .+ требует двух аргументов с плавающей запятой, простой дедукцией можно вывести, что a и b должны оба иметь тип float.

Далее, функция /. возвращает значение float, и это фактически, то же самое, что возвращаемое значение функции average, так что average должна возвращать float. Эта цепочка размышлений приводит нас к следующему описанию функции:
average : float -> float -> float

Выведение типов, очевидно, просто для коротких программ, но оно так же работает даже для больших программ. Это экономит массу времени, потому что это удаляет целый класс ошибок, вызывающих сегфолты, NullPointerException и ClassCastException в других языках (или, что важно, но часто игнорируется, предупреждения времени исполнения в иных, наподобие Perl).
+53
2778
52
amarao 120,2

Комментарии (80)

+9
ed_tsech, #
Под «Наследованием типов» я так полагаю подразумевается «выведение типов». (type inference)
0
amarao, #
М… Да, видимо, моя ошибка. Так как я сам это учу, то про эту концепцию (точнее, про её русское название) не знал. (На перевод touple как кортеж меня всё-таки хватило).
0
ed_tsech, #
Круто, что поправили, потому что «наследованием типов» и «выведение типов» все таки очень разные вещи.
0
amarao, #
Ну, моя ошибка, как переводчика. Речь всё-таки про выведение типов.
0
G0ran, #
Интересно вообще последнее время следить за функциональными языками программирования. А вообще какие перспективы у OCaml? Почему именно его начали изучать?
0
ulysses, #
По-моему, более перспективно учить F# (наследник OCaml), который есть на .NET и Mono.
+4
OdobenusRosmarus, #
Ну это на любителя. Лично меня не радует перспектива тащить моно рантайм с приложением. Да и по скорости возникают вопросы — компилятор в код у ocaml сделан очень удачно. Не уверен, что моно код сможет конкурировать с ocaml в этом.
0
impwx, #
В последнем .NET JIT работает на ура, так что со скоростью работы проблем нет, и кроме того можно использовать любые .NET-сборки, написанные на любом языке, коих существует огромное количество :)
0
mace, #
И ocaml и f# используют сборщик мусора, значит скорость будет примерно одинаковой. Единственные языки, которым дотнет уступает по скорости — это те, где нет GC, по понятным причинам.
ИМХО, если нет религиозных предубеждений перед тем, что ноги f# растут из МС, то он куда интереснее. Судя по тому, что это едва ли не первый функциональный язык, который активно продвигает крупная корпорация, он вообще имеет все шансы стать первым мейнстримовым функциональным языком.
Не холивора ради, просто посмотрите на C#, который в 2001 году выглядел практически клоном Java, а за десять лет и четыре версии из него сделали наверное самый красивый и мощный язык среди мейнстримовых. А в Java за это время сподобились разве что прикрутить дженерики, да и то на уровне языка, а не среды выполнения.
BTW, кто не знает, F# открыт и доступен под лицензией Apache.
0
mr_fresh, #
В OCaml'е есть компиляция в нативный код.
–1
mace, #
В F# это делает JIT перед каждым запуском программы. Далее выполняется все тот же нативный код. И, кстати, никто не отменял утилиты вроде ngen. Так что мимо.
0
mace, #
Mono, кстати, умеет такое вообще из коробки без всяких 3rd-party утилит: tirania.org/blog/archive/2008/Nov-05.html
+1
amarao, #
Ага. И какого размера получится код? Особенно при статическом линкинге?
–1
mace, #
Естественно больше, но согласитесь, что аргумент «результирующие бинарники будут больше» звучит все таки менее драматично чем «F# тормозной, потому что генерирует не нативный код». К тому же последний как бы и не правда.
+1
OdobenusRosmarus, #
«И ocaml и f# используют сборщик мусора, значит скорость будет примерно одинаковой.»

Это мягко говоря неправда
+1
VolCh, #
Судя по этому shootout.alioth.debian.org/u32/benchmark.php?test=all&lang=fsharp&lang2=ocaml примерно правда

+1
OdobenusRosmarus, #
Примерно правда что? Что если есть сборщик мусора, то скорость должна быть одинакова? Тогда и питоп с psyco и erlang c hipe долджны давать одинаковый результат что и моно. Ведь откровенно неправда.
0
VolCh, #
Что скорость примерно одинакова у этих языков. F# немного быстрее OCaml в 4-х тестах из 7, медленнее в одном и сильно медленнее в оставшихся 2-х (это ко второму комменту).
+1
OdobenusRosmarus, #
И кстати по ссылке, которую вы привели — ну ясно же видно, что F откровенно слабей.
0
mace, #
Аргументировать можете? Я не спорю, что возможно в среднем F# будет чуть-чуть медленнее (или быстрее), но если эта разница в пару процентов, то сами понимаете.
0
OdobenusRosmarus, #
А вот ссылку выше от тов. VolCh посмотрите. Увидите и в 4 раза и в 6 раз.
+2
amarao, #
Основная проблема F# — он генерирует не нативный код. Можно сколько угодно говорить про незначительность JIT, но это ровно до того момента, пока мы не подходим к серьёзным системным вопросам. На OCaml написан цитриксовский xapi, и одна из существенных особенностей кода на окамле — он не тащит за собой всякой дряни, а значит отлично подходит для содержимого стаб-доменов xen'а. Условно говоря, на OCaml можно написать псевдо-ядро для стаб-домена, на F# и прочих mono с грудами библиотек под жирные ядра (допустим, даже, линукса) — нет. То есть внутри уютненькой ОС — да, можно сравнивать. А если уютненькую ОС убрать?
+1
ulysses, #
Ну да, для кого-то куча библиотек это счастье, а для кого-то трагедия. Всё зависит от задач, так что я не спорю.
+2
amarao, #
Куча библиотек — это здорово. Особенно здорово, когда при необходимости, их можно статически слинковать.
0
mace, #
mono таки умеет компилится в нативный код со статически линковаными библиотеками. Я выше уже выложил ссылку на статью Мигеля двухлетней давности. Именно поэтому и возможны всякие MonoTouch'и, которые позволяют писать софт под те же айфоны, где никаких рантаймов моно никогда не было, нет и не будет.
Впрочем, наверное вы правы в том, что если уж писать софт для очень низкого уровня, то стоит выбирать для этого соответствующие инструменты.
+1
amarao, #
Ну, у меня это исключительно производственная нужда. xapi написан на OCaml и демонстрирует, что строгая типизация и компиляция таки рулит в сравнении с питоновским охламонством xend'а.
0
kmike, #
Как раз, возможно, с кодом на OCaml работать придется скоро, спасибо!)
–8
Ctacok, #
Вот зачем изобретать велосипед, с комментариями? Я про то, что они (*, а не как принято /*.
+3
ulysses, #
А в Великом и Ужасном Паскале, например, (*… *) (кроме {}). Вы, наверное, выросли на C-подобных языках. Есть куча языков, в которых не /*… */.
+2
kmike, #
Это в C так принято. А ML примерно в то же время появился, что и C. В паскале, например, тоже (* комментарии.
+3
ed_tsech, #
Надеюсь вы дочитали статью не только до того момента, где говориться о комментариях.
Просто здесь много всего, что сделано «не как принято» и комментарии думаю не самая важная из этого списка вещь.
0
amarao, #
Велосипед не мой, я просто разместил перевод.
+2
merlin-vrn, #
Python, TCL, perl, bash, большинство конфиг-файлов — везде комменты начинаются с #. На самом деле, языков с C-подобными комментариями меньшинство.
+2
ulysses, #
> double average… Сравните с более компактной версией на OCaml.
Это спорная оценка.
0
tenshi, #
а для комплексного, векторного, цветового и других видов умножений какие операторы используются?

как опредлить функцию sqr которая бы принимала на вход как целые так и вещественные числа? нельзя? тогда где тут полиморфизм?
0
ed_tsech, #
let sqrt (a:'a)
0
ed_tsech, #
1. Выведение типа:
# let hello x = «Hello » ^ x;;
val hello: string -> string = # let hello x = x;;
val hello: 'a -> 'a = (* 'a — означает любой тип *)
2. Ограничение типа:
# let hello (x:string) = «Hello » ^ x;;
val hello: string -> string = 3. Полиформизм
# let hello (x:'a) = «Hello » ^ x;;
val hello: string -> string =
И все равно этот вариант примет только строку.
# let hello (x:'a) = x;;
val hello: 'a -> 'a = (* то же самое что и let hello x = x;;, лишь с явным указанием того что доступен любой тип *)
0
tenshi, #
и что полезного сможет сделать функция с «любым типом» кроме как вернуть переданное значение?
0
ed_tsech, #
это просто пример, как указать, что функция может принимать аргумент любого типа.
0
tenshi, #
зачем указывать? тут же автоматическое выведение типов!
0
torkve, #
С ними всё просто. Как и практически всегда, с ними нельзя сделать ничего иного, кроме как выбросить либо организовать в какую-либо структуру. Например, для организации произвольного дерева нужно знать тип данных? Нет, не нужно. Вот когда это дерево потребуется сделать упорядоченным, придётся писать специализированные функции. Кроме того, есть еще другие случаи, когда функции, работающие с 'a, определены в самом языке или импортируются из внешних библиотек. Например, хэш-таблица в окамле пользуется специальной сишной функцией, которая любой тип просто сжуёт как набор байт и создаст хэш.
–1
tenshi, #
то есть строка и число, которые имеют одинаковые байты будут выдавать один и тот же хэш? суперская хэш-таблица х) сишная магия и тут во всей красе
0
torkve, #
Ну в данном случае можно сделать так:
# type num = First of int | Second of float | Triple of int*int*int;;
type num = First of int | Second of float | Triple of int * int * int
# let multiply_by_five x =
match x with
First(a) -> First(a*5)
| Second(b) -> Second(b*.5.)
| Triple(a,b,c) -> Triple(a*5,b*5,c*5);;
val multiply_by_five : num -> num = # multiply_by_five (First 42);;
- : num = First 210
# multiply_by_five (Triple (1,2,3));;
- : num = Triple (5, 10, 15)


Иначе говоря, вы можете на основе существующих типов определить некий общий тип (что-то вроде union в С, только сложнее и умнее), с которым и работать дальше. Заметьте, обобщив разрешенные входные данные в какой-то единый тип, мы можем легко и изящно определять «а какой тип данных скормили функции».
0
tenshi, #
ничего не понял кроме того, что это совсем не функция sqr и на «легко и изящно» совсем не похоже.
0
torkve, #
Ок, напишу для Вас sqr:
# let sqr x =
match x with
First(a) -> First(a*a)
| Second(a) -> Second(a*.a)
| Triple(a,b,c) -> Triple(a*a,b*b,c*c);;
val sqr : num -> num = # sqr (First 5);;
- : num = First 25
# sqr (Triple (1,2,3));;
- : num = Triple (1, 4, 9)

Разница колоссальна, да.
А что касается «легко и изящно», тут надо понять две вещи. Чтобы вызвать операцию умножения (деления, прибавления 148) для какого-то аргумента, эта операция для данного типа должна быть определена. Если складывать штаны с табуретками, надо определить соответствующий оператор в любых языках. Просто в Си (бэйсик, пхп, ява, whatever) операторы и функции могут иметь одинаковые имена, вследствие чего выбор нужной функции и автоматическое приведение типа происходит автоматически на этапе компиляции. В чём-то это удобно, зачастую ещё и опасно. Окамл требует явного указания, какую функцию мы вызываем, взамен этого он позволяет нам выбирать поведение, в зависимости от типа, не только при вызове функции, но и абсолютно в любой момент. Более того, вышеупомянутым match можно, в зависимости от шаблонов, работать не только с типами, но и с конкретными значениями, содержимыми списков:
# let check_list x =
match x with
[] -> print_endline "list is empty"
| x :: [] -> print_endline "list has one element"
| x :: y :: [] -> print_endline "list has two elements"
| _ -> print_endline "too many";;
0
tenshi, #
довольно кривой способ определения полиморфной функции
+1
susl, #
да, с перегрузкой туго во всех ML-подобных языках. что-то приходится жертвовать ради нормального выведения типов.
под полиморфизмом понимается совсем не то, что вы думаете. полиморфной функция может быть, только если она не зависит от типа.
sqr/sqrt делают разные вещи в зависимости от типа аргумента. поэтому это должны быть 2 разные функции. ведь в C бы это тоже было 2 разные функции, хоть и с одинаковым именем.
0
tenshi, #
если функция не зависит от типа, то как она может зависеть от значения? а если она не зависит и от значения, то на кой чёрт вообще это значение передавать?

полиморфизм в том и заключается, что мы указываем что нужно сделать, но конкретная реализация для заданного типа подбирается автоматически.
0
jack128, #
>> если функция не зависит от типа, то как она может зависеть от значения?
легко. в любом языке, поддерживающем параметрический полиморфизм вагон примеров. всякие List.Remove(T item) и ежи с ними.
0
tenshi, #
мы не про любой язык а про конкретный окамл.
0
susl, #
ну вот вам пример из OCaml'я: List.map, List.fold_left, List.fold_right :)
в общем функции над списком чего-то. неважно чего. они одинаково работают с любыми типами, будь то целое или кортеж.
0
VolCh, #
«Автоматически» формально к полиморфизму не относится, можно сказать, что это синтаксический сахар в некоторых языках :) Главное для полиморфизма, что можно выбрать одну из нескольких реализаций, а автоматически или ручками — не суть.
0
tenshi, #
это только в твоём вымышленном мире.
0
VolCh, #
Так выше кривой способ определения полиморфной функции или она полиморфна только в моём вымышленной мире?
0
tenshi, #
второй вариант
+2
UnknownGosu, #
комментарии в OCaml очень похожи на комментарии в Си

Комментарии (* *) — это же в чистом виде Pascal. Когда-то давным-давно IDE не умели их нормально обрабатывать (например, считали, что выражение (*) — это открытый и тут же закрытый комментарий, и неправильно разукрашивали код). На этом можно было даже всякие приколы делать вроде:
write('Hello');
(*) write(', world!'); (*)
И срабатывают оба write'а. Естественно, что вместо вывода могло быть что-нить похлеще =)
+1
UnknownGosu, #
Пардон, не так написал: срабатывает-то один write, но вот подсвечиваются как код оба, несмотря на то, что второй на самом деле закомментирован.
0
torkve, #
> Стандартная функция OCaml int_of_char casting:
Потеряли перевод слова casting.

Хорошо, что взяли для перевода этот мануал, он куда лучше, чем официальный.
0
amarao, #
спасибо, сейчас поправлю.
–1
zupernintendo, #
val average: float -> float -> float =

fun? lol
+1
Nekuromento, #
Спасибо за перевод! Больше функциональщины разной в массы!
+2
Zubchick, #
а сюда лучше не смотреть? ocaml.spb.ru/

И еще вопрос, тот окамл, что указан в статье (apt-get install ocaml) это компилятор в нативный код?
0
amarao, #
насколько я понимаю, да, и он используется в продакт системах с большим успехом. Собственно, вопрос стоит так: либо Си, либо OCaml, выбор, думаю, очевиден, ибо GC.
0
hydralien, #
За перевод, безусловно, спасибо — но местами стоило перечитать результат. Например:
+1
hydralien, #
Шайзе. Тем не менее:
«Пока что не очень понятно, почему полиморфные функции полезны, то на самом деле они очень полезны и очень распространены;» -> потеряно «если» в начале?
«Темой этого учебника является мысль, что...» -> "… мысль о том что..."
«Наша функция average, принимающая два числа с плавающей запятой и возвращающая одно число с плавающей запятой описывается так:» -> "… запятой, описывается так:"
«то мы записываем, что она возвращает» — не нужна запятая
«Как насчёт функции, которая принимает» — то же самое
«Мы можете подумать, что явное приведение уродливо, что это нудное занятие» — мы такие, мы можете. ну и первая запятая гоу хоум, да.
«оно работает даже для больших, и это экономит массу времени, потому что это удаляет целый класс ошибок» — гоу хоум обе две.
Ну и, вероятно, там кроется ещё некоторое количество. Но, повторюсь — за статью спасибо, было довольно интересно.
0
hydralien, #
вначале
+1
hydralien, #
(покорнейше прошу простить за занудство)
«Синтаксис OCaml изящен и лаконичен Вот функция»
«трёхкратно» — «троекратно»
«строка helo» — ll
«OCaml не осущетсвляет»
«плавающей запятой, используйте» =~ s/,//;
0
amarao, #
Спасибо, поправил. В чём-то профит от хабра есть. Сейчас пойду допереводить вторую часть.
0
torkve, #
Не понял, почему Вы так не любите запятые в сложносочиненных и сложноподчиненных предложениях?
Во всех местах, про которые сказано, что запятая не нужна, запятые стоят правильно.
0
hydralien, #
ну, «можете подумать, что явное приеведение уродливо» — тут как-то совсем не к месту.
«аже для больших, и это экономит» — это же не перечисление.
«времени, потому что» — тут я пожалуй неправ, но в другую сторону — «времени потому, что»
0
torkve, #
1. К месту — это сложноподчиненное предложение, запятая обязательна.
2. «Оно работает, и это экономит». Сложносочиненное предложение, запятая нужна.
3. Здесь запятую допустимо поставить двумя равнозначными вариантами, убирать её в любом случае нельзя.

Параграфы §140, §137 и §141 соответственно.
0
torkve, #
Лучше не спорьте :) Я корректурой лет 5 уже как не промышляю, и албанский плотно вошёл в мою жизнь, но русская языка ещё помню.
0
ekzo, #
почему ocalm, а не всемилюбимый haskell?
+1
amarao, #
Потому что мне нужно изучить окамл. А перевод текста — это побочный результат процесса.
0
moiseev, #
На всякий случай, для интересующихся: code.google.com/p/funprog-ru/
Перевод лекций по функциональному программированию, с примерами на ML.
+1
alexott, #
там Caml Light, который не совсем совместим с OCaml (хотя есть ветка для версии под OCaml, но там нет активности)
0
alexott, #
Может вы лучше подключитесь к переводу документации по OCaml ocaml.spb.ru/? или продолжите переводить Developing Applications With Objective Caml (http://shamil.free.fr/comp/ocaml/html/index.html)?

а то достаточно много незаконченных переводов, и я боюсь, что и у вас будет неполный перевод
+1
amarao, #
Как минимум вторая статья переведена на 80%. Дальше не знаю, посмотрим. Переводить там текста оказывается много больше, чем читать.
0
Colwin, #
Допустим, вы написали функцию, назовём её repeated, которая берёт исходную строку s, число n и возвращает новую строку, состоящую из троекратно повторённой строки s.


Не троекратно, а n-кратно.
0
amarao, #
поправил.

Только зарегистрированные пользователи могут оставлять комментарии.
Войдите, пожалуйста.