10 ms·
I found my experience trying to work with a large OCaml base a nightmare — when signatures changed in an unstable dependency (e.g. function argument removed and
by muglug 4y ago
I found my experience trying to work with a large OCaml base a nightmare — when signatures changed in an unstable dependency (e.g. function argument removed and nested inside another), the errors spat out by the typechecker were utterly incomphrehensible.
This was largely due to automatic currying in OCaml — if I have a function call "some_function arg1 arg2" and "some_function" adds a third argument, that call becomes a function that requires a single argument, arg3, but the typechecker message that tells you all of this is well-nigh unintelligible.
Switching to Rust was a blessed relief, not least because Rust has much better developer tooling and documentation (but also no automatic currying).
It made me think that OCaml is efficient and beautiful if you're the only person touching your specific codebase (which I think is true in the vast majority of cases) or if many of your colleagues are deep OCaml officionados with PhDs, but it's not a good collaborative language for the rest of us.
- octachron 4y agoDo you have a more precise example in mind? The example that you are describing should emit an error message of the form This expression has type type_of_arg_3 -> return_type but an expression of type return_type was expected which seem alright to me. (But I cannot not be called an OCaml aficionado). The type error might be delayed in sufficiently polymorphism context but that is a more infrequent occurrence (outside of functors whose error messages have been improved in OCaml 4.13 partially for this reason).
- muglug 4y agoyup, specific error message is here: https://news.ycombinator.com/item?id=31861450 https://news.ycombinator.com/item?id=31861450
- abathologist 4y agoLooks like that is exactly the error message predicted. It’s expecting a list of result values, but is getting a list of functions from some stuff to that result. IME, it’s definitely true that OCaml doesn’t have the didactic error messages that many have come to like in Rust et al. They said, imo they generally give the information needed solve problems and I like their concision. It takes a bit of time to learn to read them however.
- muglug 4y ago> It takes a bit of time to learn to read [OCaml's error messages] Yeah, and I think you could say the same of Rust when it comes to the borrow checker's messages. But in Rust's case the problem it's trying to describe is itself pretty complex. The complexity of OCaml's error messages are really not justified by the problem they're warning you about, which is itself very straight-forward.
- ahmedalsudani 4y agoThe ML languages started appearing in the 70s. Rust is a very modern language. Where do you think rust got (more than) half of its type inspiration from? Either way no one has to use any language. Personally OCaml is not my cup of tea--Haskell is. But it is a great tool and it's solving hard problems.
- muglug 4y agoI used StandardML at university in the early 2000s, and I appreciate that OCaml is an older language that heavily influenced the design of Rust. Given that history, it’s instructive to note the sorts of things (like auto-currying) that Rust did not inherit.
- ahmedalsudani 4y agoRust is a different language with a different audience. If a functional language is presented today that does not automatically curry calls, I have no interest. Just because the tool does not work for you does not mean there's a defect in the design.
- octachron 4y agoI think that it is better to not forget that expressiveness comes at the cost of a richer world of misbehaving code. If you are used to a world where functions have more than one argument and must be applied to exactly the right number of arguments and nearly never returns another function, starting a curried language suddenly throws you in a situation where many small mistakes like `plus 1` are not caught early anymore. Moreover, those delayed mistake are here to allow room for an expressiveness that you are not using yet. This is a genuinely frustrating situation. And I do hope to improve OCaml type error messages to better highlight type difference at some point in the future.
- zelphirkalt 4y agoLooks similar to what TypeScript will offer you.
- munchler 4y agoFWIW, F# is very similar to OCaml, but its error message in this case is usually quite clear. E.g. This expression was expected to have type 'int' but here has type 'string -> int'
- muglug 4y agoyes, that would be a straightforward error message! But I got the error message Error: This expression has type ((locl_ty * Tast.pos * ('ex, 'fb, 'en) Aast.expr_) list -> Result_set.t) list but an expression was expected of type Result_set.t list Type (locl_ty * Tast.pos * ('ex, 'fb, 'en) Aast.expr_) list -> Result_set.t is not compatible with type Result_set.t Which is not half as readable.
- tgflynn 4y agoIt doesn't look so bad to me. It's telling you that a list type was expected but you passed it a function that returns a list instead. You're right though that OCaml isn't the kind of language you can pick up in an afternoon.
- imajoredinecon 4y agoTiny quibble that I wouldn’t normally post but I think illustrates the readability issues here: Should that be “Result set type was expected”, not “list”?
- tgflynn 4y agoNo, I think this: Result_set.t list means a list whose items are of type Result_set.t. Also I think the notation X.t is idiomatic for a type defined by module X.
- whimsicalism 4y agoRegardless the function does not return a list.
- shadowgovt 4y agoThis mirrors my experience in SML/NJ, where for the longest time the most common error message the compiler would spit out was 'tycon mismatch' and a Google search would not tell you what a tycon was (it's a type constructor). I think, unfortunately, I've observed a pattern in much functional programming (F# being a blessed exception) that relatively excellent computer language designers suffer from the utter ineptitude of the human language competency of the tool authors. These days, I am far more interested in an elegant tool chain and support infrastructure than an elegant language.
- eckza 4y agoIf you ever do any frontend work, you should give Elm a try. The tooling is lightweight but effective; and the compiler error messages are best-in-class.
- kopecs 4y agoSadly I think SML/NJ has some of the worst messages for SML; Poly/ML and MLton both end up with something more reasonable than the dreaded Error: operator and operand do not agree [tycon mismatch]
- shikoba 4y ago> when signatures changed in an unstable dependency The problem is not OCaml here.
- muglug 4y agoI have a good point of contrast: I'm doing a similar task with an unstable Rust dependency, and when APIs change the error messages from the typechecker are crystal clear about why things are incompatible.
- shikoba 4y agoI see, you made the beginner mistake with OCaml. You should never read the OCaml error message except if all other options failed. Try to not read those messages, but just look at the line where the error occured. You can use -annot or -bin-annot when compiling and the Tuareg function caml-types-show-type in Emacs will be your best friend.
- c-cube 4y agoThis is old advice :-). Use the LSP server and dune and the error displays instantly, no further setup required.
- shikoba 4y agoI don't get it. Is it sarcasm?
- wiseowise 4y agoNo. You don't need this anymore. > You can use -annot or -bin-annot when compiling and the Tuareg function caml-types-show-type in Emacs will be your best friend. Just open OCaml program in some editor with LSP(Vim, NeoVim, VS Code, Emacs) and it will instantly show you the error.
- 4y ago
- tgflynn 4y agoFrom what I've seen of Rust though it adds its own brand of complexity due to its basic requirement for maximal efficiency which precludes having a garbage collector. I don't think I would want to use Rust unless I really needed a language with near optimal performance. OCaml on the other hand seems suitable for programs where correctness is important but performance isn't the driving concern. At least I'm considering using it for that type of application, though I'm not yet a serious OCaml programmer (still working my way through this course actually).
- wyager 4y agoThere are many similar warts in the ocaml type system. Parametric polymorphism in ocaml sucks as well. If you haven't tried haskell, you probably should. Its type system is much cleaner (and more powerful, if you want) than ocaml's, and many details of Rust are derived from Haskell.
- octachron 4y agoParametric polymorphic is the same in OCaml and Haskell. Did you meant to say that the value restriction does not play nicely with point-free programming?
- wyager 4y agoThat's one aspect of it. The ergonomics are dogshit. I remember having to use explicit quantification all the time when writing polymorphic library code. Also, without typeclasses, polymorphism is super inconvenient, to the point where it's almost exclusively used in the most critical data structures like sets and maps. It's not "the same as in haskell" except maybe in the very vague sense that they have similar underlying type theories (although ocaml's is much weaker - e.g. I remember needing to use some hacks to approximate HKTs, while haskell handles them easily).
- octachron 4y agoOCaml does not require more annotations than Haskell for polymorphic functions? Both language only require annotations in the case where inference would be undecidable (polymorphic recursions, higher-rank polymorphism, and GADTs). I know few cases where OCaml require less annotations than Haskell, I would expect the reverse to be true. And maps and sets are not the main use case for polymorphism in OCaml. Even taking in account that it sounds like you are talking about functors, maps and sets are still not the only instance of functors in OCaml.
- wyager 4y ago> OCaml does not require more annotations than Haskell for polymorphic functions? A) I am fairly confident this is not actually true in a technical sense, although it's been several years since I've thought about it B) In any case, in the (many) instances where you (by rule or convention) need to put a type signature on your function (e.g. because it's a top-level function, one that you export, etc.), it is a pain in the ass to make it polymorphic in ocaml compared to in haskell. > Even taking in account that it sounds like you are talking about functors No, I'm not, although ocaml people tend to think exclusively in "functors" (badly named, in my opinion - conflicts with the more common category-theoretic definition) because the experience of using actually parametric functions is so bad that they almost never do it.
- rwmj 4y agoWe develop a large OCaml application and find the error messages are fine. Changing signatures is an advantage when refactoring because it identifies all the places you need to make changes. In addition I greatly prefer having a garbage collector around.
- substation13 4y agoI think the issue is the compiler error messages being poor. Automatic currying is wonderful for productivity (in my experience). Elm is not really an alternative to OCaml (or Rust) but it shows how nice compiler error messages can be.
- kupopuffs 4y agoForgive my ignorance, I'd would like an example of the "functional" (hehe) benefits of automatic currying
- earth_walker 4y agoPartial application and function composition are the meat-and-potatoes of functional programming. Automatic currying is syntactic sugar that can make these two easier and clearer as it gets rid of a bunch of named arguments and lambdas. For example, I can write: foo a b c = a * b + c I can then apply this partially like this (foo2 just takes argument c): foo2 = foo 10 20 and compose it with other functions, for example like this: composed = foo2 >> bar >> baz Without currying, you'd have something like: foo (a, b, c) = a * b + c foo2 (z) = foo (10, 20, z) composed (a) = (\b -> foo2 (b)) ((\c -> bar (c)) (baz (a)))
- deleted 4y ago[deleted]
- agent281 4y agoBefore I start, let me say that I'm on mobile and I don't know how to post code on this site. I'm assuming backticks work. --- One benefit is that functions are more reusable. Functions with more arguments can be partially applied and used where applicable. For instance, if you wanted to increment the values in a list you might do something like this in python: map(lambda x: x + 1, [1,2,3]) But in an ML like language you can do something like: map((+ 1), [1,2,3]) Because plus is actually a function that takes two arguments and can be curried down to one argument. Additionally, you can think of currying as a form of dependency injection. For example, if you wanted to inject a database client and a logger before running a query you might do something like this: def queryer(db, logger): def _q(query): logger.info("before query") results = db.fetch(query) logger.info("after query") return results return _q rows = queryer(db, logger)(query) You could write it a bit more simply in this hypothetical ML like language: queryer db logger query = logger.info("before query") results = db.fetch(query) logger.info("after query") results MLs don't require parens/commas for function calls, but you can use parens to force a particular execution order. The lines below have the same result, but some produce and execute intermediate curried functions rows = queryer db logger query rows = (queryer db) logger query rows = ((queryer db) logger) query It's a bit contrived, but you have the flexibility to provide some arguments based on the call site rather than the definition site. I.e., the first example was broken up into 2 function calls at the definition site, but the second example could have 1, 2, or 3 function calls. --- I hope this makes sense and I wish I could preview my comment to see if it was formatted reasonably!
- int_19h 4y agoA big part of it is that currying is idiomatic, which, I think, is a design mistake that most functional languages carry as a historical baggage. But multi-argument functions can just as well be represented as functions of tuples, even in OCaml (and, if I remember correctly, it is idiomatic in SML), and then you get a proper error message about the type of argument rather than the result.