神刀安全网

OCaml Language Sucks (2007)

Record field naming hell
Cannot have two record types in the same file that have a field with the same name (and before version 3.09, you could not have a record field named contents !)
Syntax
Pretty much unreadable; especially
  • the bizarre rules to decide how many expressions after else belong to it
  • the extra parens necessary to write negative numbers because - is interpreted as a function , not a part of a token; e.g.,
    List.fold_left (fun x y -> if x > y then x else y) -1 nums ;; This expression has type int -> int list -> int but is here used with type int
  • there are actually three mildly different syntaxes:
    1. the official one is described in the manual
    2. a superset thereof is accepted by the compiler ocamlc
    3. something similar (but ever so slightly different) is accepted by the preprocessor Camlp4 (e.g., it accepts List.map [1;2;3] ~f:fun x -> x , which is also accepted by the top-level, but not the compiler)
No Polymorphism
Cannot add an int to a float

– need an explicit cast. This is an obvious deficiency, but there are more subtle ones. E.g, the following code does not work:

type t = MyInt of int | MyFloat of float | MyString of string let foo printerf = function   | MyInt i -> printerf string_of_int i   | MyFloat x -> printerf string_of_float x   | MyString s -> printerf (fun x -> x) s

because the first statement makes OCaml think that printerf has type (int -> string) -> int -> 'a instead of the correct ('a -> string) -> 'a -> 'b . (Yes, there are many workarounds

, which make the problem look even more ugly – like the old Perl problem of "how do I create an array of file-handles").

In general, this type collapse makes it impossible to use polymorphic functions with higher order functional arguments (like printerf above) with variant types.

Another example (trimmed down from actual production code which had to be rewritten):

type 'a t1 = { s1 : int; s2 : 'a; } let make f = f let bad = [ make (fun x -> x.s1); ] type a = { a : int; } let good = [ make (fun x -> x.s2.a); ]

Here good works while bad does not. (to add insult to injury, it works in the top-level, but cannot

be compiled in a file).

Inconsistent function sets
There is List.map2 but no Array.map2 . There are Array.mapi and Array.iteri , but no Array.fold_lefti . Somehow :: is a syntax , not an infix version of the (nonexistent!) function List.cons .
No dynamic variables
Without dynamic variables for default values, optional ~ arguments are next to useless.
Optional ~ arguments suck
  • The default values of the optional arguments cannot depend on positional arguments because of the calling conventions: to make function call delimiters optional, every function call for a function with optional arguments is required to end with a positional argument.
  • Values do not match:   val foo : ?arg:string -> unit -> unit is not included in   val bar : unit -> unit

    even though foo can be called exactly like bar , so one has to use (fun () -> foo ()) instead of foo .

  • The default values of optional arguments restrict the possible type the argument may take. E.g., in
    let max_len ?(key=(fun x -> x)) strings =   Array.fold_left ~init:0 strings ~f:(fun acc s ->     max acc (String.length (key s)))

    the type of key is string -> string , not 'a -> string as it would have been if the argument were required.

Partial argument application inconsistency
If f has type ?a:int -> b -> c -> d then f b has type c -> d , not ?a:int -> c -> d as one might have expected from the fact that one can write f b ~a:1 c . Moreover, types a:int -> b:int -> unit and b:int -> a:int -> unit are incompatible even though one can apply functions of these types to the exact same argument lists! Moreover, until 3.10, functions foo and bar compiled while baz did not:
let foo l = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x) l let bar = (fun l -> ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x) l) let baz = ListLabels.fold_left ~init:0 ~f:(fun s (_,x) -> s + x)

Why? Actually, the latter is a good sign: Ocaml is improving!

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 ofsilent 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.

Silent name conflict resolution
If modules A and B both define t (a very common type name!), and you open both modules, you are not warned that one of t ‘s is shadowed. Moreover, there is no way to figure out in the top-level which module defines a specific variable foo .
Order of evaluation
The forms are evaluated right-to-left (as they are popped from the stack), not left-to-right (as they are written and read). E.g., in f (g ()) (h ()) (that’s (f (g) (h)) for those who hate the extra parens), h is called before g . This is done for the sake of speed and because in the pure functional world the evaluation order is inconsequential. Note that OCaml is not a pure functional language, so the order does matter!
No object input/output
Top-level can print any object, but this functionality is not available in programs. E.g., in Lisp one can print any structure to a file in a human-readable (and editable!) form to be read later. OCaml has to resort to cumbersome external libraries (like Sexp) to accomplish something like that. Compare Lisp:
(with-open-file (f "foo" :direction :output)   (write x :stream f :readable t :pretty t))

with Ocaml:

call_with_open_output_file "foo" (fun f ->   output_string f     (Sexp.to_string_hum        (sexp_of_list (sexp_of_pair sexp_of_foo sexp_of_bar) x)))

Note that OCaml has more parens than Lisp! Note also that the Lisp code is generic (it will write any x ) while OCaml is only for an association list – you have to write a separate function for every type you save. This lack of pre-defined generic printing also complicates debugging (nested structures are hard to print) and interactive development.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » OCaml Language Sucks (2007)

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮