Krytyka OCaml: czy nadal jest aktualna?

15

Jestem kompletnym nowicjuszem z OCaml. Niedawno natknąłem się na tę stronę, wymieniając sporo krytyki wobec OCaml.

Widząc, że strona jest dość stara (2007): który z wymienionych tam punktów jest nadal aktualny? Na przykład: czy nadal jest prawdą, że nie można wydrukować ogólnego obiektu?

Chcę wyjaśnić, że nie szukam dyskusji na temat wyrażonych w nich opinii. Pytam, czy wymienione informacje, takie jak fakt, że liczby całkowite są przepełnione bez ostrzeżeń, nadal są poprawne dla nowszych wersji OCaml

Andrea
źródło
5
Głosuję za zamknięciem tego pytania jako nie na temat, ponieważ meta.programmers.stackexchange.com/questions/6417/…
Philipp
3
Jest widelec gdzie te mogłyby już naprawiono: tryfsharp.org
Den
7
Oczywiście opinia w tym eseju, który podłączyłeś, jest słuszna, ale to, czy te krytyki są dla ciebie istotne, jest zupełnie inną sprawą. Zauważ, że autor pochodzi ze wspólnego Lisp i dlatego ma wartości Lispish. Jeśli przyjmiesz inne podejście do OCaml niż porównanie go z Lisp (np. „OCaml to Haskell dla zwykłych śmiertelników” lub „Gdyby C był językiem funkcjonalnym, miałbyś OCaml”), okaże się, że jest to o wiele bardziej satysfakcjonujące. Pomimo wszystkich swoich wad OCaml jest świetnym językiem i mimo wszystko zachęcam do zagłębienia się w nim.
amon
5
O ile mi wiadomo, nie ma sposobu na wykrycie przepełnienia liczb całkowitych w sprzęcie , więc aby je wykryć , trzeba będzie wprowadzić kontrole środowiska wykonawczego; ale autor odrzucił użycie „bignum” na podstawie wydajności! Skarga dotycząca pisania statycznego sprowadza się do stwierdzenia, że ​​pasy bezpieczeństwa są złe, ponieważ możesz myśleć, że nie możesz umrzeć w wypadku samochodowym. Skarga na niezmienność modułu mówi, że chce on małpować rzeczy - praktykę antymodularną i podatną na błędy. „Małe zoo typu” nie ma nic wspólnego z wnioskowaniem typu. Oczywiste jest, gdzie leżą jego uprzedzenia .
Doval
2
@Doval: Oczywiście można wykryć przepełnienie sprzętu. Jednak radzenie sobie z tym jest kwestią oprogramowania.
Mason Wheeler,

Odpowiedzi:

13

Ten artykuł jest omawiany w kilku miejscach:

Podsumowując: tak, OCaml nie jest Lispem i nie, nie jest doskonały (co to znaczy?). Nie sądzę, aby punkty wymienione w poście na blogu były przydatne dla codziennych programistów O'Caml.

Po przestudiowaniu O'Caml uważam, że jest to interesujący język, który może pomóc w tworzeniu programów, o których nawet nie odważyłbyś się pisać, powiedzmy w C / C ++ / Java: na przykład, spójrz na Frama-C .

Aby uzyskać aktualny opis O'Caml, zachęcam do zapoznania się z jego funkcjami : język promuje silne techniki sprawdzania typu statycznego, które pozwalają implementacjom skupić się na tworzeniu wydajnych, ale bezpiecznych środowisk uruchomieniowych.

Ważne : nie jestem ekspertem OCaml: jeśli jesteś jednym z nich i widzisz, że napisałem coś strasznie złego, popraw mnie. Zmienię odpowiednio ten post.

Sprawdzanie typu statycznego

  • Fałszywe poczucie bezpieczeństwa

    To prawda, ale oczywiste.

    Wpisywanie statyczne daje dowody, którym możesz zaufać w odniesieniu do podzbioru właściwości programu. O ile nie zaakceptujesz formalnego postępowania, przeciętny program (niebędący zabawkami) będzie podlegał błędom programistycznym, które można zaobserwować tylko w czasie wykonywania.

    Wtedy można zastosować techniki sprawdzania dynamicznego: kompilator OCaml ma flagi do generowania plików wykonywalnych z informacjami debugowania i tak dalej ... Lub może generować kod, który ślepo ufa programatorowi i usuwa informacje o typie w jak największym stopniu. Programiści, którzy chcą solidnych programów, powinni wyraźnie wdrożyć kontrole dynamiczne.

    To samo dotyczy np. Common Lisp, ale odwrócone: najpierw typy dynamiczne, z opcjonalnymi deklaracjami typów i dyrektywami kompilatora po drugie.

  • Kilka podstawowych typów

    Nadal obowiązuje: podstawowy język nie zmienił się (lub nie dramatycznie).

  • Ciche przepełnienie liczb całkowitych

    Jest to norma w większości języków, że przepełnienie liczb całkowitych jest sprawdzane ręcznie. Nie znam żadnej biblioteki, która sprawdziłaby operacje typu, aby sprawdzić, czy może dojść do przepełnienia.

  • Niezmienność modułu

    1. Autor wspomina Functors, ale nie widzę, jak jego przykład nie może zostać zaimplementowany. Czytając rozdział dotyczący modułów pierwszej klasy https://realworldocaml.org , wydaje się, że modułów można używać do komponowania i budowania nowych modułów. Oczywiście modyfikacja istniejącego modułu wymaga modyfikacji kodu źródłowego, ale znowu nie jest to rzadkie wśród języków programowania.

    2. Semantycznie funkcje są kompilowane INLINE”

    Wątek reddit powyżej nie zgadza się, mówiąc, że wiązanie jest rozwiązywane w czasie łącza. Jest to jednak szczegół implementacji i myślę, że podkreślony Semantycznie odnosi się do sposobu rozwiązywania funkcji. Przykład:

     let f x y = x + y ;;
     let g a b = f b a ;;
     let f x y = x * y ;;
     exit (g 2 3) ;;
    

    Powyższy program kompiluje, a po uruchomieniu zwraca 5, ponieważ gjest zdefiniowany w pierwszej wersji f, tak jak gdyby funkcja wywołująca gwstawiała wywołanie do f. Nawiasem mówiąc, nie jest to „złe”, jest po prostu zgodne z zasadami cieniowania nazw O'Caml.

    Podsumowując : tak, moduły są niezmienne . Ale można je również komponować .

  • Polimorfizm powoduje błędy typu Run-time

    Nie mogę odtworzyć wspomnianego błędu. Podejrzewam, że to błąd kompilatora.

Bez makr

Rzeczywiście, nie ma makr, ale preprocesory (OcamlP4, OcamlP5, ...).

Słabość językowa

  • Rekordowe nazewnictwo piekła

    To prawda, ale powinieneś używać modułów:

    1. Dwa pola dwóch rekordów mają tę samą etykietę w OCaml
    2. Rozwiązywanie nazw pól
  • Składnia

    Nadal obowiązuje (ale tak naprawdę to tylko składnia).

  • Bez polimorfizmu

    Nadal obowiązuje, ale w jakiś sposób są ludzie, którzy wolą to zamiast wieży numerycznej Lispa (nie wiem dlaczego). Przypuszczam, że to pomaga w wnioskowaniu typu.

  • Niespójne zestawy funkcji

    Zobacz projekt Dołączone baterie OCaml . W szczególności BatArray , na przykład map2dla tablic.

  • Brak zmiennych dynamicznych

    Można wdrożyć:

    1. http://okmij.org/ftp/ML/dynvar.txt
    2. http://okmij.org/ftp/ML/index.html#dynvar
  • Opcjonalne ~ argumenty do bani

    Ze względu na ograniczenia językowe nie można mieszać argumentów opcjonalnych i słów kluczowych we wspólnym Lisp. Czy to znaczy, że jest do bani? (oczywiście można to zmienić za pomocą makr (patrz np. moja odpowiedź )). Zobacz dokumentację O'Caml dla opcjonalnych i nazwanych argumentów w O'Caml.

  • Częściowa niespójność aplikacji argumentów

    Nie wydaje mi się, żeby to było naprawdę denerwujące w praktyce.

  • Czytelność arytmetyki

    Może się przydać, ale jeśli wolisz, możesz użyć R lub Pythona do rozwiązywania problemów numerycznych.

  • Rozwiązywanie konfliktów nazw niemych

    Nadal obowiązuje, ale pamiętaj, że jest to dobrze udokumentowane.

  • Brak wejścia / wyjścia obiektu

    Nadal obowiązuje.

Implementacja, biblioteki

Te zmieniają się każdego dnia: nie ma ostatecznej odpowiedzi.

Wreszcie,

„Powinieneś wypróbować OCaml (lub jeszcze lepiej Haskell), nawet jeśli uważasz, że jest do bani i nie planujesz go używać. Bez tego twoje wykształcenie informatyczne jest niekompletne, podobnie jak niekompletne bez niektórych Lisp i C (lub , jeszcze lepiej, Zgromadzenie) ”.

... nadal obowiązuje.

rdzeń rdzeniowy
źródło
Dziękuję, rzuciłem okiem na linki, ale tak naprawdę nie pytam, czy te opinie są uzasadnione. Pytam, czy fakty nadal istnieją. Na przykład: czy istnieje sposób na wydrukowanie dowolnego typu danych algebraicznych? Czy nadal jest dokładne, że liczby całkowite przepełniają się bez ostrzeżeń? Czy istnieje dziś sposób działania na plikach i usuwania ich bez konieczności pisania za każdym razem, gdy płyta główna zajmuje się zamykaniem plików w przypadku błędów?
Andrea
@Andrea Zredagowałem swoją odpowiedź.
rdzeń rdzeniowy
1
„... są ludzie, którzy wolą to zamiast wieży numerycznej Lispa (nie wiem dlaczego). Przypuszczam, że to pomaga w wnioskowaniu typu”. Bingo System typów SML i OCaml wymaga, aby każde wyrażenie miało tylko jeden typ. Przeciążenie operatorów matematycznych przez SML jest wyjątkiem wbudowanym w język. Dotyczy to również Haskell; umożliwienie tego rodzaju przeciążenia było motywacją do stworzenia klas typów. Chodzi o to, że dla każdego typu można mieć tylko jedną instancję klasy typu. Nie można również ślepo przekonwertować liczby całkowitej na liczbę zmiennoprzecinkową o równej wielkości - liczba zmiennoprzecinkowa 64-bitowa ma tylko 54 bity dokładności.
Doval
@Doval Co powiesz na coś takiego jak wpisanie wieży numerycznej do rakiety? Czy możemy sobie wyobrazić bibliotekę OCaml, która wykorzystywałaby klasy typów lub uogólnione algebraiczne typy danych (GADT) w celu zapewnienia polimorficznych operatorów matematycznych? Odnośnie konwersji: nie wszystkie operacje są możliwe, ale niektóre są i można je wpisać.
rdzeń rdzeniowy
2
@Doval: „Re: polimorficzne operatory matematyczne, nie sądzę, że jest jakikolwiek sposób bez implementacji klas typów Haskell”. F # ma polimorficzne operatory matematyczne bez klas typów.
Jon Harrop,
7

Widząc, że strona jest dość stara (2007): który z wymienionych tam punktów jest nadal aktualny?

  • Fałszywe poczucie bezpieczeństwa . To nonsens.

  • Kilka podstawowych typów . OCaml ma teraz bajty i tablice bajtów, ale nie ma wbudowanych ciągów Unicode, 16-bitowych liczb całkowitych, liczb całkowitych bez znaku, 32-bitowych liczb zmiennoprzecinkowych, wektorów lub macierzy. Biblioteki innych firm zapewniają niektóre z nich.

  • Ciche przepełnienie liczb całkowitych . Bez zmian, ale nigdy nie było problemu.

  • Niezmienność modułu . Jego zalecenie, aby funkcje i moduły były modyfikowalne, jest ponurą odpowiedzią na Lisp i naprawdę złym pomysłem. Możesz zastąpić moduły, includejeśli chcesz, ale oczywiście nie możesz ich mutować.

  • Polimorfizm powoduje błędy typu Run-time . Jest to duży problem z OCaml i nie został naprawiony. Gdy twoje typy ewoluują pod względem równości polimorficznej, porównywanie i mieszanie zaczną się nie powieść, gdy napotkają takie typy jak funkcje, a debugowanie problemu jest bardzo trudne. F # ma świetne rozwiązanie tego problemu.

  • Bez makr . Jak na ironię, kiedy to napisał, OCaml faktycznie miał pełne wsparcie dla makr, ale teraz postanowili wycofać tę funkcję.

  • Owijarki . To był prawdziwy problem i nie został naprawiony. Nadal nie ma try ... finallykonstrukcji w języku OCaml i żadne opakowanie nie implementuje jej w stdlib.

  • Miejsca . Bez zmian, ale bez problemu.

  • Rekordowe nazewnictwo piekła . Zbuduj swój kod poprawnie za pomocą modułów.

  • Składnia . Bez zmian, ale bez problemu.

  • Bez polimorfizmu . Było to głównie nonsensem, kiedy to napisał i nic się nie zmieniło.

  • Niespójne zestawy funkcji . OCaml nadal nie ma consfunkcji. W porządku. Nie chcę rzeczy Lisp w moim języku, dziękuję.

  • Brak zmiennych dynamicznych . To było dobre w OCaml. To wciąż dobra rzecz w OCaml.

  • Opcjonalne ~ argumenty do bani . Opcjonalne argumenty rock. Odznaczyłem Microsoft, aby kazał im dodać opcjonalne argumenty do F #.

  • Częściowa niespójność aplikacji argumentów . Co?

  • Czytelność arytmetyki . Zmieniło się to odkąd przestałem używać OCaml ~ 8 lat temu. Najwyraźniej teraz możesz to zrobić Int64.((q * n - s * s) / (n - 1L)).

  • Rozwiązywanie konfliktów nazw niemych . Próbował w pełni rozwinąć oprogramowanie w REPL, tak jak w Lisp. Nie rób tego w OCaml. Używaj plików i kompilacji wsadowej odwołując się do REPL tylko do testowania, uruchamiania kodu jednorazowego i interaktywnych obliczeń technicznych.

  • Kolejność oceny . To było złe, kiedy to napisał. Kolejność oceny jest niezdefiniowana w OCaml.

  • Brak wejścia / wyjścia obiektu . Przytoczył bibliotekę strony trzeciej, która już rozwiązała ten „problem”.

  • Kompilator zatrzymuje się po pierwszym błędzie . Co?

  • Brak śladu stosu dla natywnie skompilowanych plików wykonywalnych . Naprawiony.

  • Debuger jest do bani . Nigdy nie korzystałem z debuggera. Statyczne sprawdzanie typu wyłapuje prawie wszystkie moje błędy.

  • GC jest do bani . Odkryłem, że GC OCaml jest znakomity, z wyjątkiem jednego poważnego problemu: globalna blokada uniemożliwia programowanie równoległe.

  • Brak dorozumianych deklaracji forward . Wzajemna rekurencja jest jawna z założenia we wszystkich ML. Jedynym dziwactwem jest to, że typedefinicje są domyślnie rekurencyjne, podczas gdy letwiązania nie są domyślnie rekurencyjne.

  • Runda funkcyjna jest nieobecna . OCaml wciąż ma standardową zawartość, ale biblioteki stron trzecich, takie jak Core Jane St roundi przyjaciele, zapewniają .

  • Listy . List.mapwciąż nie jest rekurencyjny. Przesłałem łatki, aby naprawić poważne błędy tego typu i musiałem czekać lata, zanim pojawią się w wydaniach. Oczywiście listy są niezmienne. I tak powinny być.

  • Prędkość . Wierzę, że czasy kompilacji dużych wariantów polimorficznych zostały naprawione.

  • Dopasowywanie wzorów . Triumf nadziei nad rzeczywistością. Społeczność Lisp tego nie zrobiła. Stąd moja 10 zasada: każdy wystarczająco skomplikowany program Lisp zawiera ad-hoc, nieformalnie określoną i pozbawioną błędów implementację połowy kompilatora dopasowywania wzorców OCaml.

Na przykład: czy nadal jest prawdą, że nie można wydrukować ogólnego obiektu?

Kiedy napisał, że nie można po prostu zrobić:

print value

ale możesz wywołać ładną drukarkę z najwyższego poziomu jako wywołanie biblioteki, podając jej niezbędne informacje o typie. Było też makro, którego można użyć do opisywania struktur danych w celu automatycznego generowania ładnych drukarek.

Jon Harrop
źródło
Dopasowanie wzorca: kod źródłowy OCaml i optymima odnoszą się do tego samego papieru: „Optymalizacja dopasowania wzorca”. Powiedziałbym, że nie można tutaj realistycznie zastosować ani „ad-hoc”, „pomyłki” ani „nieformalnie określonego”. „F # ma świetne rozwiązanie tego problemu”: Naprawdę chciałbym zobaczyć trochę więcej szczegółów na ten temat, jeśli to możliwe. Odpowiedź jest dobra, ale przeklinanie, gdy mówi się o niej, consnadaje zły ton (oryginalny artykuł jest rantem, ale nie trzeba go z niego kopiować).
coredump
2
@coredump: „Naprawdę chciałbym zobaczyć trochę więcej szczegółów na ten temat, jeśli to możliwe”. F # ma zdefiniowany przez użytkownika polimorfizm ad-hoc na niektórych operatorach i funkcjach. Możesz więc używać +na liczbach całkowitych, zmiennoprzecinkowych i kompleksach, ale możesz także definiować własne typy i dodawać przeciążenia +do pracy na swoim typie. Zapewnia to zwięzłość i czytelność Lisp lub Haskell z przewidywalnie dobrą wydajnością SML lub OCaml, osiągając coś, czego nie robi żaden inny język.
Jon Harrop,
@coredump: „Kod źródłowy OCaml i optymima odnoszą się do tego samego papieru”. Techniki opisane w tym artykule są w całości oparte na systemie typu ML. System typów, którego Lisp nie ma. Optima nie robi i nie może wiele z tego, co opisano w tym artykule. Wystarczy spojrzeć na punkt 4.2 „Korzystanie z informacji o wyczerpaniu” W Lisp nie ma informacji o kompletności, ponieważ nie ma typów wariantów. Na przykład OCaml wybiera między zagnieżdżonymi skokami lub tabelą wysyłki w oparciu o liczbę liści, ale ta informacja nie jest znana w Lisp.
Jon Harrop,
Lisp i OCaml są zaprojektowane do różnych rzeczy (np. Dynamiki, programowania obrazowego). Ale Lisp ma system typu, różny od Hindley-Milner i implementacje nie wykorzystać go podczas kompilacji. Spójrz na tę sesję SBCL z przykładami z sekcji 4.2 artykułu. Kompilator jest już sprawdzany przez kompilator na podstawie wnioskowania o typie i deklaracji. Optima może dodać kod specyficzny dla implementacji dla makropolecenia w czasie kompilacji (lub VOP SBCL), aby mieć inne strategie, ale nie ma wystarczających zachęt, aby to zrobić.
rdzeń rdzeniowy
Jak to się ma do zdefiniowanego przez użytkownika algebraicznego typu danych?
Jon Harrop