LINUX.ORG.RU

OCaml и его состояние

 , ,


0

6

Хотелось бы узнать по сабжу. На сколько жив/пригоден для продакшена? Какие основные области применения? Как обстоят дела с вебом? Его плюсы/минусы по сравнению с Haskell, Scala, Clojure?

Окамл? Спокон веку поддерживался валютными спекулянтами Jane Street и «академией» (пресловутый Coq, знаете ли).

Некоторое время назад в OCaml серьёзным образом перепилили рантайм. ИМХО, уже на деньги с AI-хайпа.

Так что Окамл живее всех живых.

Претензии к Окамл абсолютно стандартные (ровно такие же претензии к Java, Go, Erlang/Elixir, Haskell и проч.)

Предположим, вы используете Окамл в продакшене. Всё здорово, но в какой-то момент ваше поделие начинает падать, течь, тупить (нужное подчеркнуть).

Вы уверены что вам лично и вашей команде достанет компетенций, чтобы нырнуть в грязные и вонючие потроха языкового рантайма?

anonymous
()
Ответ на: комментарий от anonymous

Clojure - динамически типизированный язык, в отличии от haskell, scala, ocaml, как следствие ошибки, которые были бы отловлены в любом из этих языков, будут всплывать в рантайме.

Есть конкретные примеры из опыта или просто мантру повторяешь?

Pwner
()
Ответ на: комментарий от anonymous

Предположим, вы используете Окамл в продакшене. Всё здорово, но в какой-то момент ваше поделие начинает падать, течь, тупить (нужное подчеркнуть).

Вы уверены что вам лично и вашей команде достанет компетенций, чтобы нырнуть в грязные и вонючие потроха языкового рантайма?

Увы, но если проблемы с рантаймом, то с этим справятся далеко не все. И искать язык по этому принципу довольно странно, так как проблемы рантайма условно раста не будут проще проблем условно окамла

anonymous
()
Ответ на: комментарий от Pwner

Clojure - динамически типизированный язык, в отличии от haskell, scala, ocaml, как следствие ошибки, которые были бы отловлены в любом из этих языков, будут всплывать в рантайме.

Есть конкретные примеры из опыта или просто мантру повторяешь?

Да, есть, а что? Это не лунное откровение, для получения которого нужно слетать на ракете на Луну, где-то его откопать, и прилететь обратно на Землю с ним, не потеряв по дороге. Если задача перекладывания json-а из одного места в другое ещё может быть худо-бедно решена на динамически типизированных языках, то в задачах посложнее это начинает очень сильно чувствоваться.

Один из красноречивых примеров - это синтаксический анализ программного кода. Если синтаксис посложнее лиспа(а это множество языков), то типизация очень помогает при разборе кода. Например, возьмём javascript. Предположим, я забыл или не узнал какой там у js синтаксис для for в стиле си. Я открываю тип, и вижу,

type ('M, 'T) t = {
    init : ('M, 'T) init option;
    test : ('M, 'T) Expression.t option;
    update : ('M, 'T) Expression.t option;
    body : ('M, 'T) Statement.t;
    comments : ('M, unit) Syntax.t option;
}

И вот я уже сразу понимаю, что в for в круглых скобочках есть три необязательные части, а телом for может быть как операторный блок, так и одна инструкция, при этом выражение обязательно. Разумеется, это же можно было узнать и из мануала по самому js, однако во-первых благодаря хорошим типам мануал обязательно читать только при создании парсера, а во-вторых, компилятор меня поддержит, и если я забуду, что блок инициализации может отсутствовать, и буду пытаться работать с ним без проверки его существования, то вместо ошибки в рантайме, которая может случится не у меня, а у пользователя или же у меня, но спустя время, когда я забуду про неё, и буду долго по всему проекту её искать, так вот, ocaml вместо этого сразу же во время компилияции сообщит мне об этом. Я пытался работать с Syntax Tree на динамически типизированных языках, и скажу, что это ужасно. Очень легко допустить ошибку, которая всплывёт спустя время.

Точно так же, можно увидеть, что в js есть for в стиле си, for in и for of.

Разумеется, это характерно не только для работы с исходным кодом, но и для множества других сфер. При работе с тем же самым 2-3-4 деревом компилятор тоже так же подскажет, если вдруг кто перепутает узел 2 и 4.

Мне нравится то, что на окамле я могу просто определить тело функции, без явного указания всех типов. Это очень помогает, когда нужно написать какой-то прототип, а потом расширить его функциональность. Вот несколько упрощённый пример: есть какая-то функция

let fun_name = function
  | Some v -> v
  | None -> 0

Я написал прототип, мне понравилось. Потом потребовалось расширить функционал. Я просто поменял одну единственную строку, компилятор убедился что всё работает, и я продолжил разработку

let fun_name = function
  | Some v -> v
  | None -> 0, 1

Если писать такой код на haskell/rust/go/scala/…, то потребуется по всему стеку вызова проходить и менять типы, в то время как я пишу почти как на динамически типизированном. Однако, если где-то типы не сошлись, и компилятор это увидел, например

let result = List.map [Some 1; Some 2] ~f:fun_name

то он тут же мне об этом сообщит. В Clojure после такого небольшого рефакторинга нужно либо надеяться на то, что тесты покрывают изменённый кусок, либо сидеть и вручную всё проверять, и идти писать тесты. Особенно это неудобно, когда нужно накидать несколько прототипов и сравнить их. Таким образом на ocaml я могу не отвелкаясь от программирования наслаждаться выводом типов и строгой типизацией, в то время как в других языках у меня будет куча ручной работы, и возможно даже ошибки в время выполнения.

anonymous
()
Ответ на: комментарий от anonymous

С Растом в продакшене не знаком.

В C++ падения с дедлоками прекрасно разбираются без какого-то специфичного инструментария и какой-то специфичной подготовки.

С протечками сложнее, но разбирать протечки на языке с менеджером памяти занятие куда более мерзкое.

Если разработчики не ленятся «-fsanitize=address -fsanitize=undefined», то наступает почти полная идилия.

Для охоты за тормозами ничего кроме poor man’s profiler и не требуется (впрочем, это зависит от области применения).

И все вокруг понимают, что если возник какой-то косяк, то виновен ты и только ты. Так как больше и некому… И огромное количество косяков выявляется отсмотром git blame.

anonymous
()
Ответ на: комментарий от anonymous

В C++ падения с дедлоками прекрасно разбираются без какого-то специфичного инструментария и какой-то специфичной подготовки.

Угу без всякой подготовки, достаточно просто лет 10 - 15 программировать на с++.

anonymous
()
Ответ на: комментарий от anonymous

нужно либо надеяться на то, что тесты покрывают изменённый кусок, либо сидеть и вручную всё проверять

Надеяться и оценивать работу на глазок - это какая методология разработки? Я под впечатлением, что сейчас повсеместно принято инкрементально описывать тестами ожидаемое поведение, потом его реализовывать. На этом фоне достоинства типизации (если они есть) совсем не очевидны.

Это не лунное откровение, для получения которого нужно слетать на ракете на Луну.

Тогда тебя не затруднит вместо пространных рассуждений в вакууме привести конкретные цифры, показывающие, что использование типизированного вместо динамически проверяемого, при прочих равных (компетенция, опыт с инструментом, область разработки), повышает твою продуктивность на M>0, не смотря на необходимость ублажать компилятор, а количество ошибок сокращает на N>0.

Пока что мои наблюдения показывают, что по факту профит не наблюдается. И, похоже, не я один такой глупенький:

Robert Martin:

I found that type issues simply never arose. My unit tests kept my code on the straight and narrow. I simply didn’t need the static type checking that I had depended upon for so many years [1].

Bruce Eckel:

To claim that the strong, static type checking constraints in C++, Java, or C# will prevent you from writing broken programs is clearly an illusion (you know this from personal experience). In fact, what we need is Strong testing, not strong typing [1].

ADT в OCaml сильно меняет расклад сил и твои, например, сортировки гарантированно сортируют? Не сортируют. В Idris’е для такого юзкейса неплохо было бы написать доказательство доказательства. Похожая ситуация в Spark (High Integrity Software [2003], Barnes, страница 375).

Pwner
()
Последнее исправление: Pwner (всего исправлений: 1)
Ответ на: комментарий от anonymous

Украду пример у одного лисперокамлиста.

  • Lisp (критикуемый за нечитабельность арифметических выражений):
(/ (- (* q n) (* s s)) (1- n))
  • Оригинальное выражение:
(q * n - s * s) / (n - 1)
  • OCaml:
let ( +| ) a b = Int64.add a b
let ( -| ) a b = Int64.sub a b
let ( *| ) a b = Int64.mul a b
(Int64.to_float ((q *| (Int64.of_int n)) -| (s *| s))) /. (float n)

Красота.

Pwner
()
Ответ на: комментарий от anonymous

Угу без всякой подготовки, достаточно просто лет 10 - 15 программировать на с++.

Периодически размышляю над тем, что а не было ли моей самой большой ошибкой жизни – прислушаться к неосиляторскому аргументу про «да на изучение этого C++ уйдёт 5-10 лет жизни». Десятки маргинальных нетакусиковых ЯП в конечном счёт не дали ровно ничего вопреки обещаниям о расширении сознания и пр. Последнее надо было добирать изучением фундаментальных наук.

Pwner
()
Ответ на: комментарий от anonymous

Ну так покажи, как надо, чтобы было красиво, я то на OCaml’е не пишу, вот отсюда взял пример. Вся секция оттуда (и контекст):

Arithmetic's readability
    Lisp is often blamed for its "unreadable" representation of arithmetic. OCaml has separate arithmetic functions for float, int, and int64 (and no automatic type conversion!) Additionally, functions take a fixed number of arguments, so, to multiply three numbers, you have to call Int64.mul twice. Pick your favorite:

    (/ (- (* q n) (* s s)) (1- n))

    (q * n - s * s) / (n - 1)

    (Int64.to_float (Int64.sub (Int64.mul q (Int64.of_int n)) (Int64.mul s s))) /. (float n)

    The above looks horrible even if you open Int64:

    (to_float (sub (mul q (of_int n)) (mul s s))) /. (float n)

    which is not a good idea because of silent name conflict resolution. An alternative is to define infix operators:

    let ( +| ) a b = Int64.add a b
    let ( -| ) a b = Int64.sub a b
    let ( *| ) a b = Int64.mul a b
    (Int64.to_float ((q *| (Int64.of_int n)) -| (s *| s))) /. (float n)

    but this comes dangerously close to the horrors of "redefining syntax" (AKA "macro abuse") while not winning much in readability.

Или ты тактик: и ответил, и не показал, что всё равно гавно получается?

Pwner
()
Ответ на: комментарий от Pwner

Я на окамле не пишу и не знаю, но этот конкретный претензионный код высосан из пальца.

Код на лиспе использует универсальный тип для чисел и операции с этим универсальным типом.

Код на окамле работает с конкретным типом Int64, для которого не определены (инфиксные) алгебраические операции.

Если не нужна точность Int64 (в промежуточных результатах опреаций), то можно тупо все преобразовать к float и писать красивые формулы:

(* окамл и вообще мл-языки не знаю от слова совсем *)
let q' = Int64.to_float q
let n' = Int64.to_float n
let s' = Int64.to_float s

(q' *. n' -. s' *. s') /. (n' -. n')

(* операции с точкой "+.","-.","*.","/." определены для float *)

anonymous
()
Ответ на: комментарий от anonymous
(* операции с точкой "+.","-.","*.","/." определены для float *)

Можно и так:

module FloatOps = struct
  
  let ( + ) = ( +. )
  let ( - ) = ( -. )
  let ( * ) = ( *. )
  let ( / ) = ( /. )

end

...

  let q = Int64.to_float q in
  let s = Int64.to_float s in
  let n = float n in
  
  let r = FloatOps.((q * n - s * s) / (n - 1.)) in
  Printf.printf "%.2f\n" r

и кроме «1.» никаких отличий от изначальной формулы.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

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

anonymous
()
Ответ на: комментарий от Pwner

На «изучение» C++ и всей жизни не хватит. Но лет через пять, cppreference и Стандарта станет мало, и в ход пойдут тяжёлые наркотики документы WG21.

Полагаю, это справедливо для любого «большого» языка (того же Окамла, например).

Язык можно изучать тольоко исключительно в рамках профессиональной деятельности и жёсткого «продакшена». В конце-концов, дело ведь не в знании конструкций языка, а в умении решать задачи на данном языке в конкретной предметной области и за определённое время.

anonymous
()
Ответ на: комментарий от anonymous

К слову, хаскель не требует писать сигнатуры для функций верхнего уровня.

В простых случаях да. В сложных требует, но увы, я не настолько хорошо знаю, чтобы с ходу привести пример сложного случая.

anonymous
()
Ответ на: комментарий от anonymous

В C++ падения с дедлоками прекрасно разбираются без какого-то специфичного инструментария и какой-то специфичной подготовки.

С протечками сложнее, но разбирать протечки на языке с менеджером памяти занятие куда более мерзкое.

Для создания утечки нужно ещё постараться. Это должно быть что-то необычное, например попытка слинковать с ещё одним языком. А как на плюсах бороться с повреждением памяти?

anonymous
()
Ответ на: комментарий от anonymous

Ну, это странное, конечно, утверждение. Разбираться с утечками в C++ на порядки сложнее, чем в той же Java / Scala. Просто несравнимые вещи. Тот аноним явно погорячился

anonymous
()
Ответ на: комментарий от anonymous

В оправдание того анонима могу только заметить, что утечки в Java, возможно, проще, чем в той же Scala. Например, Scala держит ссылку на параметр конструктора. Еще якобы бесконечный Stream держит ссылку на this. Все это приводит к офигительным утечкам. Кстати, поэтому Stream стал депрекейтид.

В старой Java таких утечек было гораздо меньше. Она и топорнее. А вот про новую Java с ее недо-ФП сказать ничего не могу.

Что касается C++, сравнение, как я выше написал, просто несерьезное. Там до фига надо знать и о том, как работает стек, и еще кучу-кучу всего бесполезного, лишнего и ненужного.

anonymous
()
Ответ на: комментарий от Pwner

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

У тестов есть одна фундаментальная проблема: кто будет сторожить сторожей? Я привёл пример выше с парсингом js: мне не нужно виртуозно владеть js и знать полностью его синтаксис, мне достаточно полагаться на типы, они мне подскажут, если я забуду какую-то его вариацию. Типы могут доказать наличие ошибки в программе, тесты же могут показать отсутствие ошибок в некоторых путях. Если во время написания тестов забыть указать какую-то вариацию синтаксиса, то ошибке не узнают до тех пор, пока она не всплывёт, например на проде. Для покрытия того, что проверяется статической типизацией, нужно быть намного внимательнее, намного осведомлённиие, чем если снизить количество тестов и полагаться на типизацию. У вас есть опыт работы со сложными структурами данных на динамически типизированных языка? Подскажите, как вы справляетесь с тем, чтобы не забыть граничные варианты?

Был один случай, когда дата в get и post запросах передавалась в разных форматах. Выяснилось, что при post дата валидитовалась, и парсилась в объект времени, присваивалась в orm и сохранялась в базу данных, а вот get возвращалась в виде строки из базы данных. В таких случаях очень легко замылится глазу, когда будет сравнивать строки в тестах.

Тогда тебя не затруднит вместо пространных рассуждений в вакууме привести конкретные цифры

Я не веду статистики, но количество рантайм ошибок очень сильно различается. Я некоторое время назад писал небольшую программу, работавшую с AST, и если в динамически типизированных языках я в repl шаг за шагом собирал из небольших кусочков выражение, и перескочи я несколько шагов, и там был либо null pointer exception либо что-то в этом роде, потом, бывало, что это же выражение падало в каком-то месте, написанном чуть иначе, чем я расчитывал, то в статически типизированном языке мне достаточно было его написать в редакторе и посмотреть на проверку типов, которая проходит примерно со скоростью моей печати, мне не нужно было даже запускать код, чтобы понять, работает он или нет, а когда я запускал его, то он работал без вопросов. Меня это весьма впечатлило.

не смотря на необходимость ублажать компилятор, а количество ошибок сокращает на N>0.

Не могу ручаться за скалу, джаву или другой язык, но в случае с окамлом я почти не ублажаю компилятор. Да, есть моменты, которые нужно написать особым образом, но это условно пара строк на несколько тысяч или даже десятков тысяч. В то же время, это отлично помогает, против того же null pointer exception, undefined is not a function и прочих радостей динамической типизации. Проблема в том, что если может придти null, то окамл (хаскель точно, за скалу не ручаюсь), подскажет, что нужно не забыть проверку. Динамически типизированный язык здесь никак не поможет.

И, похоже, не я один такой глупенький:

Обе ссылки, и в обеих на стороне статической типизации c, c++ и java. У них весьма слабая система типов, а учитывая постоянные приведения типов они работают как динамически типизированные, поскольку в случае чего, ошибки в них точно так же возникнут в рантайме. В ocaml нет понижающего приведения типов. Опыт написания кода на окамле отличается от опыта написания на джаве, или плюсах. Код на окамле гораздо локаничнее и выразительнее.

anonymous
()
Ответ на: комментарий от Pwner

ADT в OCaml сильно меняет расклад сил

GADT, если точнее. Да, это одна из важнейших фич, для типизированного кода. Сейчас даже вечные сторонники динамической типизации вынуждены признать важность этого, и вносят изменения в свои языки, правда крайне криво. Отголоски этого видны в C#, Type Script, python, php, ruby, lua, … (я не про все языки знаю). Что самое смешное, вместо c# майкрософт мог бы изначально сделать f#, где всё было бы по нормальному, но они окольными путями спусят кучу лет всё же пришли к важности null safety и всё же реализовали её, но как и полагается майрософту, через костыль.

и твои, например, сортировки гарантированно сортируют? Не сортируют

Зависимые типы, это интересная тема, и я хотел бы в неё углубится. Я посмотрел несколько лекций по идрису, доказывать сложно, и далеко не все вещи будут доказываться.

anonymous
()
Ответ на: комментарий от Pwner

Lisp (критикуемый за нечитабельность арифметических выражений):

Как правило, за синтаксис критикуют те, кто больше ни за что кртиковать не может.

Полагаю, что автор примера на лиспе малость ошибся

(/ (- (* q n) (* s s)) (1- n))
(/ (- (* q n) (* s s)) (- 1 n))

Красота.

  1. благодаря каррированию аргументы у функций можно опустить.
(* before *)
let ( +| ) a b = Int64.add a b
(* after *)
let ( +| ) = Int64.add
  1. благодаря затемнению, совершенно не обязательно писать такие странные операторы. Опять, же можно не инлайнить преобразование типов, если хочется. Мне лень перепечатывать всё это выражение, укажу только одну переменную. Выше уже приводились примеры того, как это может выглядеть.
(* before *)
let ( +| ) = Int64.add
(* after *)
let ( + ) = Int64.add

(* before *)
(Int64.to_float ((q *| (Int64.of_int n)) -| (s *| s))) /. (float n)
(* after *)
let n = float n in
(Int64.to_float ((q *| (Int64.of_int n)) -| (s *| s))) /. n
  1. такая запись является следствием отсутствия ad-hoc полиморфизма, это не плохо и не хорошо, это такая особенность окамла. В случае окамла это во-первых упрощение вывода типов, а во-вторых, можно сразу понять, где идёт сложение чисел, а где что-то другое. В хаскеле ad-hoc полиморфизм есть, и там знак плюс не обязательно означает сложение чисел, это может быть что угодно, хоть пересечение множеств.

  2. В окамле можно сразу же понять, что данное выражение работает с числами. В хаскеле понадобится языковой сервер и возможно hoogle. Лично меня несколько напрягает последний факт, но не будь компилятор хаскеля таким тормозным, и не приходись бороться с ленивыми вычислениями и некоторыми другими подходами языка, я бы уже попробовал хаскель в маленьких задачах. А вот понять, что делает лисп решительно невозможно. Может быть с числами работает, может что-то с матрицами, может вообще что-то левое. И хорошо, когда данный пример маленький, а как насчёт большого и сложного проекта?

  3. Как я понимаю, в коде на лиспе притаилось приведение типов. Очень неприятно, когда какое-то выражение перестало сходится, из-за того, что где-то внутри поменялись типы.

anonymous
()
Ответ на: комментарий от Pwner

Периодически размышляю над тем, что а не было ли моей самой большой ошибкой жизни – прислушаться к неосиляторскому аргументу про «да на изучение этого C++ уйдёт 5-10 лет жизни».

Аноним выше прав: невозможно прочитать книжку по c++ и пойти решать порчу памяти, дедлоки, отладку библиотечного кода в уже скомпилированном виде, или когда ошибка есть в релизе но отстутствует в дебаге. Это огромный массив знаний, ошибок и опыта.

Я вот недавно решил почитать, как в плюсах памятью управляют. Куча материала на начальном уровне: вот есть new и delete, а подкапотных подробностей нет. Вот зачем современные программы алоцируют виртуальную память гигабайтами? Как там работает освобождение памяти?

anonymous
()
Ответ на: комментарий от anonymous

Очень неприятно, когда какое-то выражение перестало сходится, из-за того, что где-то внутри поменялись типы.

Это как раз менее вероятно в CL/Scheme, поскольку там будут использоваться, по возможности и/или необходимости, наиболее точные и/или вместительные типы (рациональные дроби, длинные числа, комплексные числа и т.п.).

Другое дело, что и производительность соответствующая.

korvin_ ★★★★★
()
Ответ на: комментарий от anonymous

Например, Scala держит ссылку на параметр конструктора. Еще якобы бесконечный Stream держит ссылку на this. Все это приводит к офигительным утечкам. Кстати, поэтому Stream стал депрекейтид.

Я думаю что ты ошибаешься, если параметр не объявлен как val и мы не в case class, то ссылку на него Scala не держит.

Что касается Stream, то его объявили deprecated из-за того, что его первый элемент был не ленивый, ленивым был только tail. Пришедший ему на замену LazyList полностью ленив.

И еще – что касается поиска утечек, то какой-то особой разницы нет. Их ищут не путем чтения кода, а путем анализа дампа памяти, например в Eclipse Memory Analyzer. Конкретный механизм «удержания» не имеет особого значения, и Scala от Java ничем на этом уровне принципиально не отличается. Разве что не удобно работать с конструкциями вроде связного списка, но к этому быстро привыкаешь.

maxcom ★★★★★
()
Ответ на: комментарий от korvin_

Нет, (1-)[http://clhs.lisp.se/Body/f_1pl_1_.htm#1-] — это функция, которая вычитает 1 из аргумента.

И как я должен был до этого догадаться? Это даже страшнее операторов хаскеля, они хотя бы визуально из кода выделяются, хотя бы понятно, что их нужно в hoogle засунуть. Не самый хороший пример читаемости.

anonymous
()
Ответ на: комментарий от anonymous

И как я должен был до этого догадаться?

В CL ограниченное количество стандартных char'ов, имеющих специальное значение, и литералов, которые как-то особо читаются reader'ом, всё остальное — просто symbols, связанные со значением/функцией/макросом/особой формой. Просто смотришь в HyperSpec вместо Hoogle. Ну или IDE подскажет.

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

Кстати, видно, что лексер лора работает неправильно. «1-» это один токен, а не два. Символично, что такой простой язык, и не смогли правильно распарсить.

clojure

(1- n)

lisp

(1- n)

scheme

(1- n)
anonymous
()
Ответ на: комментарий от anonymous

На «изучение» C++ и всей жизни не хватит. Но лет через пять, cppreference и Стандарта станет мало, и в ход пойдут тяжёлые наркотики документы WG21.

Полагаю, это справедливо для любого «большого» языка (того же Окамла, например).

Масштаб катастрофы очень сильно отличается. Есть огромные языки - c++, haskell, есть средние - ocaml, есть маленькие - Core ML, go. В случае окамла особо много изучать не надо, нужно будет лишь иногда подсматривать в документацию.

В конце-концов, дело ведь не в знании конструкций языка, а в умении решать задачи на данном языке в конкретной предметной области и за определённое время.

Если человек всю жизнь писал на C++98, а придёт в компанию, где пишут на c++17, то во-первых, ему для начала нужно будет понять, а что-же там написано, а во-вторых, написать код так, чтобы пройти ревью. По сути, нужно будет переучиваться, хотя и там и там C++/

anonymous
()
Ответ на: комментарий от anonymous

В лиспе - «числовая башня», когда любое приведение чисел не теряет точности. Кстати, известный лиспер Всеволод Дёмкин лисп хвалит, в том числе, за это в своей книге (книгу смотрел по диагонали не читая - пока не дошли руки).

Так что, что одним будет видеться минусом, то другим кажется большим плюсом

А F# смотрели? Или от микрософта воротит? Впрочем, также может воротить и от французской INRIA. Чем они лучше?

anonymous
()
Ответ на: комментарий от anonymous

А F# смотрели? Или от микрософта воротит? Впрочем, также может воротить и от французской INRIA. Чем они лучше?

Я немного знаю о нём, но от погружения отталкивает неприязнь к микрософту, крайне агресивной компании, то объявляющей свободный софт раком, то в санкциях участвующей, то проталкивающей edge вместо в том числе firefox. Как я понял, компилятор f# нужно брать у мс, со всеми вытекающими.

Из тех вещей, которые я заметил на первый взгляд: f# полагается на рефлексию, например при работе с json не нужно объявлять для каждого типа функции преобразования в и из json, в то время как ocaml наоборот удаляет всю информацию о типах во время сборки и по этому полагается на кодогенерацию/ручное написание. Рефлексия может быть привлекательна, как магия, когда всё сразу работает, без дополнительного кода, но вот когда возникают ошибки, то понять что именно пошло не так - сложно. У меня был негативный опыт с рефлексией в c#, и мне это очень не понравилось.

В f# значащие пробелы, как в haskell, как в python. Это значит, что если пробелы куда-то денутся, то код перестанет собираться, в то время как ocaml наоборот готов компилировать код с любым форматированием. Однако, у окамла есть утилиты для автоформатирования, и их можно подключить прямо к IDE, из коробки поддержка идёт в spacemacs. Это значит, что о форматировании кода почти не нужно беспокоится(иногда приходится поправлять ошибки автоформатирования). Теперь мне нехватает автоформатирования в других языках, работающего прямо во время ввода кода. Однако, из-за синтаксиса f#, haskell, python реализовать автоформатирование для этих языков будет невозможно. мс взял и ухудшил эту часть языка.

ocaml

let mid x y =
  let sum a b =
    a + b in
  (sum x y) / 2

f#

let mid x y =
  let sum a b =
    a + b
  (sum x y) / 2

ocaml - aot, f# - jit, для компиляции кода во время выполнения потребуются дополнительные ресурсы.

Емнип в f# важен порядок файлов в директории, в противном случае код не соберётся. Нельзя просто так поменять содержание a.fs и b.fs местами, если один из них зависит от другого.

f# развивается в тени c#, завися от принятых в нём решений, не являясь полностью самостоятельным, емнип в вопросе той же асинхронности они были вынуждены ждать как это реализуют в c#. ocaml развивается сам по себе, не будучи скованым языками с иными парадигмами.

Но это без глубокого погружения, думаю различий гораздо больше. Это при том, что изначально f# был передран с окамла.

anonymous
()
Ответ на: комментарий от korvin_

Очень неприятно, когда какое-то выражение перестало сходится, из-за того, что где-то внутри поменялись типы.

Это как раз менее вероятно в CL/Scheme, поскольку там будут использоваться, по возможности и/или необходимости, наиболее точные и/или вместительные типы (рациональные дроби, длинные числа, комплексные числа и т.п.).

Если вместо 2 будет 2 1/2, то этого уже хватит. Данное поведение будет полезно для ограниченного набора задач, вроде решения математических уравнений.

anonymous
()
Ответ на: комментарий от anonymous

Ну, я предпочитаю явно прописывать порядок файлов в проекте f# - тогда зависимости будут определены.

А так, к inria французов у меня такое же отвращение, как к американскому микрософту. Хрен редьки не слаще

anonymous
()
Ответ на: комментарий от anonymous

есть средние - ocaml,

Это спорно, скорее сложный, хотя и проще чем тройка C++, haskell и scala, слишком много парадигм уже поддерживает, и многие фишки сильно отличаются от мейнстримных: модули первоклассные, параметризованные, ООП сильно непохожий на другие со структурной типизацией, эта же структурная типизация и во многих других местах (что кстати и приближает по читабельности к динамическим языкам), сложная система типов с GADT (его даже хаскель прямо не поддерживает), а с пятой версии еще и эффекты для многопоточки. Единственное чего нет из сложного это макросы, но это компенсируется прямой поддержкой ast препроцессоров (camlp5, ppx).

anonymous
()
Ответ на: комментарий от anonymous

В f# значащие пробелы, как в haskell, как в python.

Это необязательно, можно отключить, раньше и было отключено по умолчанию.

f# развивается в тени c#, завися от принятых в нём решений, не являясь полностью самостоятельным, емнип в вопросе той же асинхронности они были вынуждены ждать как это реализуют в c#

Насколько я помню наоборот, асинхронность намного раньше появилась в F# чем в C#. И кроме нее тоже многие фишки перекочевали из F# в C#, кажется даже генерики первоначально разрабатывались в F#. Раньше прямо говорили что F# экспериментальная площадка для идей.

anonymous
()
Ответ на: комментарий от anonymous

Это спорно, скорее сложный, хотя и проще чем тройка

Проблема в том, что другие языки не особо проще. Если в окамле сложность видна сразу же, то там она будет на какое-то время спрятана. В php, например, есть некоторые особенности, связанные с автозагрузкой, и это можно долго не замечать, пока не потребуется мокнуть класс - из-за того, что класс загружается однократно, то для того, чтобы мок сработал, нужно чтобы этот класс загружался впервые, и если он в текущем процессе уже загружался, то нужно его запускать в новом процессе. Об этом написано в документации к тестам, но если эту строку не увидеть, то можно долго не понимать, почему код не работает.

ООП сильно непохожий на другие со структурной типизацией

Как минимум в type script так же

anonymous
()
Ответ на: комментарий от anonymous

К слову, хаскель не требует писать сигнатуры для функций верхнего уровня. Просто так принято, да и без этого трудно будет понять код

Ещё как требует. Если у тебя в коде GADTs, Type Families и прочая подобная магия, вывод типов становится невозможным.

hateyoufeel ★★★★★
()
Ответ на: комментарий от anonymous

А так, к inria французов у меня такое же отвращение, как к американскому микрософту.

Чем они таким отметится успели?

Coq переименовали, потому что интернетные лесбиянки слишком боятся пенисов чтобы на нём писать :(

hateyoufeel ★★★★★
()
Ответ на: комментарий от anonymous

В хаскеле ad-hoc полиморфизм есть, и там знак плюс не обязательно означает сложение чисел, это может быть что угодно, хоть пересечение множеств.

За instance Num для чего-то странного, что не соответствует законам этого класса, тебя быстренько отпердолируют. Если конечно это не твой собственный говнокод.

hateyoufeel ★★★★★
()