[HN Gopher] OCaml Programming: Correct and Efficient and Beautiful
___________________________________________________________________
OCaml Programming: Correct and Efficient and Beautiful
Author : philonoist
Score : 278 points
Date : 2022-06-23 12:38 UTC (1 days ago)
(HTM) web link (cs3110.github.io)
(TXT) w3m dump (cs3110.github.io)
| m_a_g wrote:
| I learned some Haskell years ago but it didn't stick with me.
| Lately, I want to get back into functional programming to see
| whether I'm missing out on something. This book looks nice, and
| so does OCaml. Any suggestions on books, courses, or resources on
| this subject?
|
| Also, just to get proper motivation, why should I learn
| functional programming as a capable Software Engineer?
| mejutoco wrote:
| IMHO the main reason to learn functional programming is to
| learn to untangle state and behaviour.
| kubb wrote:
| You can strengthen the skill of thinking about your programs in
| terms of higher level abstractions, which allows you to have an
| overview and reason about larger systems.
| yakak wrote:
| Dan Grossman's Programming Languages is a great 3 language
| online course. It gives you the opportunity to compare standard
| ML, ruby and racket.
|
| Before that course I was already grouping languages by
| inheritances from C or Lisp families, but really started to
| recognize and appreciate ML more.
| bhrgunatha wrote:
| Berkeley's Programming Languages and Compilers references this
| book and you get to apply OCaml to something non-trivial.
|
| https://inst.eecs.berkeley.edu/~cs164/fa21/
| tgflynn wrote:
| It doesn't appear from your link that the course materials
| are available online though.
| bhrgunatha wrote:
| On the schedule page, there are links to further material.
| I don't think you can do the drills or exams, but you _can_
| do the homework if you have a github account.
| tremon wrote:
| You should learn multiple paradigms to broaden your horizon, so
| you are able to approach a problem from multiple angles and
| decide on the best design approach.
|
| I don't write in a functional language in my day job, but I'm
| much more aware of global state and side-effects in my code
| than before. We mainly write in Python and C#, but I encourage
| my team members to dabble in SQL (for dataset manipulation) and
| Haskell (for functional purity). If anything, it makes them
| understand LINQ better.
| ufo wrote:
| For Ocaml in particular, one thing that is pretty cool about it
| is the module system. Although we unfortunately can't use this
| module system in other languages, it taught me some interesting
| lessons about abatract data types.
| ufo wrote:
| Another thing I recommend everyone to learn at least once is
| sum types aka algebraic data types. And how they are the dual
| to class based dispatch (what some call the "expression
| problem"). Sum types aren't exclusively found in functional
| languages, but they are certainly prominent in them.
| yawaramin wrote:
| The OP is a great book to learn OCaml. There is also a
| corresponding set of lecture videos on YouTube.
| water8 wrote:
| OCaml Sucks and I'm almost certain this is the same professor who
| was at UCLA beating this horse to death at least 10 years ago. It
| has no performance or coding benefit for teams larger than a few.
| tgflynn wrote:
| > It has no performance or coding benefit for teams larger than
| a few.
|
| That must be why Jane Street uses it then.
| darthrupert wrote:
| And pretty much only them.
|
| Perhaps some day we'll learn how little the programming
| language matters for anything beyond coding.
| gjadi wrote:
| When you look for job in OCaml, you can find several
| fintech and blockchain related companies.
| cmrdporcupine wrote:
| I'm so glad you took time out of your day to come here and tell
| us this.
| blackerby wrote:
| A quick scroll through the comments and it looks like no one is
| actually talking about the book itself, so I will. I came across
| it at a really good time in my computer science self education.
| It helped me learn about some essential data structures and
| algorithmic analysis. The video lectures interspersed in the text
| augment the text and vice versa. The sections without videos were
| tougher for me to digest, but are written clearly and bear
| revisiting and study. If you, like me, are looking for a good
| next step after Grossman's PL MOOC, I recommend spending some
| time with this incredible free resource.
| haskellandchill wrote:
| I'm enjoying the discussion of commutative diagrams,
| abstraction functions, and representation invariants in section
| 6.3. It is very well motivated and easy to understand.
| kubb wrote:
| My introduction to programming course in my university was in
| OCaml. It was all downhill from there when it comes to the
| programming languages I had to use.
| newsoul wrote:
| Which university? Does the course have a public webpage?
| pwiecz wrote:
| Not the original commenter, but my first year course of
| Introduction of Programming 15 years age was also using
| OCaml. There were two groups of students, standard one was
| using Pascal, and the functional one using OCaml. I was
| studying on Warsaw University and the lecture notes are in
| Polish: https://mimuw.edu.pl/~kubica/wpf/wpf.pdf
| newsoul wrote:
| Thanks! 15 years, that's quite a long time ago.
| pwiecz wrote:
| Yup. I really loved all the functional stuff I've learned
| there from OCaml to SML+Extended ML, to Haskell.
|
| Unfortunately, I haven't had a chance to professionally
| program functionally since the n. :(
|
| It was all Java and C++. Though I like C++, maybe it's a
| Stockholm syndrome. ;)
| kubb wrote:
| That was exactly the course that I took at the WU :) Fond
| memories.
| lmm wrote:
| Cambridge does this (or did when I was there);
| https://www.cl.cam.ac.uk/~lp15/MLbook/ is the book for the
| course (available online).
| afarviral wrote:
| Seems its better to start with javascript so you can have the
| reverse experience.
| kubb wrote:
| Then I would probably be writing HN posts claiming that
| languages with advanced type systems and functional
| programming are too hard and impractical, and that they are
| trying to be clever and cool for no reason.
|
| But, partly thanks to that course, I'm able to pick up any
| paradigm, and my opinion on what's better is informed by
| knowledge of both things. I'm also able to structure
| imperative code better than my "imperative only" colleagues.
| bobthechef wrote:
| zaptrem wrote:
| I took this class last semester. Michael Clarkson is an awesome
| professor. After learning OCaml I want pattern matching in
| JavaScript! Beyond the language itself, it absolutely made me a
| better programmer.
| toastal wrote:
| Pattern matching without ADTs though loses a lot of the power.
| The fact that you'd still have to handle null/undefined cases
| means it's still annoying to use and hardly exhaustive.
| 0x69420 wrote:
| erlang/elixir programmers would disagree -- we match on ad-
| hoc dynamically typed tuples that serve broadly the same
| purpose as ADTs all the time, where the only exhaustiveness
| you get is the degenerate case ie `_ -> shit_the_bed()`
|
| especially common are pattern-matching assignments
| (technically `=` _is_ the "match operator") that
| idiomatically behave much like matches on the left hand side
| of ` <-` in haskell do notation, i.e. inline runtime
| assertions on structure
|
| these communicate programmer intent really well! you could
| probably have them in static land in a non-`monadFail`
| context too, but you'd want dependent types or something lest
| you go the way of typescript and resign yourself to
| unsoundness
|
| in fact it's so nice that i feel pain in any dynamic language
| without full-fat pattern matching, like when i have to write
| disgusting if-else chains in complex nix expressions
| franz_kafkaagh wrote:
| I write Elixir for work and I love most things about the
| language.
|
| The pattern-matching would still be made better with static
| checks. It sucks to have a function blow up at runtime
| because of something as trivial as args being swapped or
| some code somewhere changing its return types. Someone
| brought up Gleam earlier, but god why with that syntax?
|
| I'd still rather have dynamic checked pattern matching over
| the alternative.
| 0x69420 wrote:
| dialyzer and a liberal sprinkling of typespecs gets a
| decent chunk of the way there, meanwhile the problem with
| gleam and friends is the fact that they're altogether new
| languages -- the pragmatic solution would be a restricted
| subset of erlang (that a likewise restricted subset of
| elixir could comfortably compile down to) where your
| module _has_ to be fully typed, but you would need some
| pretty gnarly logics to handle things like "yes, when
| this function mashes these two iolists together in this
| way, it's still an iolist" etc
| pjfin123 wrote:
| I took this class!
| mark_l_watson wrote:
| That is a great format for an online book! The text looks like it
| could be read on its own for a quick review, and the hundreds of
| short embedded videos dig into detail.
| usrn wrote:
| I really want to like ML but I have some complaints about OCaml
| in particular:
|
| The stdlib is _very_ weak so everyone uses this third party
| library. That really rubs me the wrong way.
|
| All structures share a namespace for their elements which is
| super wacky.
| sshine wrote:
| I spent a lot of time on Standard ML in university.
|
| OCaml was always portrayed as the engineer's alternative for real
| applications.
|
| I spent some time brushing up on OCaml a few years ago using
| Exercism.io.
|
| While OCaml is a personal "top tier" language, I'd always prefer
| Haskell, Rust or Scala.
|
| Type classes / traits just seem to beat a higher-order module
| system for me.
| cmrdporcupine wrote:
| I learned SML/NJ and OCaml around the same time and for some
| reason I found SML more pleasant. I particularly liked the SML
| "Basis" library, it felt really well designed to me.
|
| Still, I'd pick either over Scala. Superior compilation times,
| cleaner syntax, less complex. Haskell also just feels
| excessively clever.
|
| I had hoped Rust would be "OCaml but for systems programming"
| and it sort of is, -- but the borrow checker and memory safety
| features, as neat as they are, add a lot of mental overhead.
| frou_dh wrote:
| SML definitely has a more tastefully designed syntax also,
| and not only because it's a smaller grammar. OCaml syntax is
| the "scuffed" version of SML syntax, as the kids say!
|
| Unfortunately to be a real-world SML user these days is a
| very isolating proposition, because although there are some
| really nice implementations (MLton, PolyML), to a first
| approximation there are zero libraries.
| xvilka wrote:
| SML is the dead end because specification wasn't updated
| for decades.
| xyproto wrote:
| I also find it hard to spot why OCaml is the natural "step up"
| for creating real world applications instead of Haskell, Rust,
| Clojure or even Kotlin, C++, Python and Go.
| toolslive wrote:
| let's see: - OCaml vs Haskell: eager vs
| lazy (=> memory consumption is more predictable) -
| OCaml vs Rust: OCaml has a GC (=> comfort) OCaml has tco
| (could not resist this ;) ) - OCaml vs Clojure:
| vastly superior typing system. (=> less bugs) - OCaml
| vs Kotlin: no JVM needed. - OCaml vs C++: more
| safety. once it compiles it will not segv. - OCaml vs
| Go: vastly superior typing system. (=> less bugs) -
| OCaml vs Python: vastly superior type system, way better
| performance.
|
| The biggest risk of doing OCaml (or Haskell or Rust) for
| extended periods of time is that you will be unable to hide
| your feeling of superiority towards (fe) a python developer.
| contificate wrote:
| I think the languages you selected in your final remark sum
| it up for me. If one is truly taken by functional
| programming, much of their mental model starts to revolve
| around algebraic (inductively defined) data types and
| structural recursion (aided by pattern matching) over them
| - such that mentally reducing the set of candidate
| languages really does become an implicit process of
| questioning: "does X have ergonomic, statically-typed,
| discriminated sums?".
|
| Lots of mainstream languages simply fail this test and make
| it feel like intellectual poverty or that there's extreme,
| turgid, boilerplate required for a weak imitation of the
| features (see the idiomatic class-hierarchy encoding of
| ADTs in large projects - such as LLVM - in C++, for
| example).
|
| It's absolutely no surprise that more mainstream languages
| are picking up a match-like construct and lighter encodings
| of discriminated sums. So, it really comes to what else you
| wish to be burdened with when compiling an OCaml-like
| mental model to X in your head: Tagged unions a-la C? Class
| hierarchies for ADTs in C++? The travesties of
| std::variant? Monad transformers in Haskell? Lazy
| evaluation? No static typing at all? Caring about memory
| management and ownership? Box and Arc-ing recursive
| components of ADTs? Writing your own arena allocator?
| Compiling to the JVM? Spotty TCO support?
|
| OCaml is just a nice, fairly simple (at its core, at
| least), language that captures the essence of the ML
| family, compiles to native (and bytecode and, transitively,
| JavaScript), has great tooling (opam, dune, ocamllex,
| memhir, etc.), great libraries (official LLVM bindings, for
| example), and a great community. Lots of OCamlers are well
| aware of other potential languages that somewhat suit their
| style of programming, they just don't want to be burdened
| by the other stuff.
| [deleted]
| water8 wrote:
| OCaml vs All: Good luck finding developers that will write
| quality code and not cost a fortune each.
| yawaramin wrote:
| If Jane Street can teach OCaml to traders, I can teach it
| to developers. That's not a concern for anyone other than
| a sweatshop.
| akhmatova wrote:
| _If Jane Street can teach OCaml to traders, I can teach
| it to developers._
|
| You can if they are motivated to learn. Unless you are
| offering Jane Street levels of compensation; and in
| return, the candidate is willing to believe, or pretend
| to believe that company's shtick about OCaml being so
| categorically superior as a general-purpose development
| language so as to leave all the others in the dust --
| most likely they won't be.
| yawaramin wrote:
| Or if, you know, they were hired to work on a project
| where they get paid a salary. That seems to incentivize
| most devs.
| akhmatova wrote:
| Not "a salary", but a Jane Street salary and resume cred.
| Otherwise you just won't find that many takers.
| toolslive wrote:
| Actually, what I found more impressive is that they also
| got them to use emacs.
| akhmatova wrote:
| It's not getting things done that matters. It's feeling
| superior to others that matters.
| ladis_washerum wrote:
| shikoba wrote:
| Hey, it's not a feeling. It's a fact.
| hutzlibu wrote:
| It is certainly a fact, that you feel superior.
|
| I would like to confirm that fact, by comparing
| productive output..
| wiseowise wrote:
| Define "productive output".
| frizlab wrote:
| What about Swift?
| Zababa wrote:
| On the server, less ecosystem than OCaml. Long compile
| times. A big part of its "market share" is taken by Rust
| instead.
| KurtMueller wrote:
| For now, a good language if you want to develop apps for
| the mac ecosystem.
| twh270 wrote:
| Not sure what you mean by "- OCaml vs Kotlin: no JVM
| needed." as Kotlin has support for native and JavaScript
| compilation, and WASM via native.
| wiseowise wrote:
| Did you even try it? It still requires Java, Gradle and
| Kotlin compiler to work. Also, good luck making it work
| without using Intellij.
| toolslive wrote:
| so it's either a JVM or either no libraries?
| pjmlp wrote:
| Kotlin has a crude support for native, and they had to
| reboot the implementation as they got clever and went
| with a memory model incompatible with JVM and JS GCs,
| thus making code portability an headache.
| thayne wrote:
| Maybe it's changed in the year or so since I looked at
| kotlin, but my impression when I did look at it was
| support for anything other than the JVM was definitely
| second class
| 0des wrote:
| throwamon wrote:
| > vastly superior typing system. (=> less bugs)
|
| I have no horse in this race, but claiming it to be "vastly
| superior" and implying "less bugs" makes it sound like this
| is a logical consequence, when you're actually staying on
| one side of an endless debate that to me doesn't have clear
| winners. For instance, from the little I've learned about
| Clojure, they claim the lack of a "vastly superior type
| system" is a feature, not a bug, and it's a result of a
| fundamental difference in some beliefs about how to write
| correct software.
|
| From this I wonder how misrepresentative your other
| comparisons are as well. But don't get me wrong, OCaml is
| probably my "favorite language I've never actually used"
| (I've never "actually used" Clojure in "real projects"
| either).
| toolslive wrote:
| Take any open source python project on github (or others)
| look at the list of issues and count the number of
| 'NoneType' has no attribute ... instances. All these
| could have been avoided by a decent type system. I rest
| my case ;)
| tgflynn wrote:
| I don't think I've ever seen anyone seriously argue
| against the claim that strong typing systems at least
| prevent many types of bugs. Now people may think that
| they are more productive in a language with weak types
| but that's a different consideration.
| camgunz wrote:
| This was on HN the other day:
| https://github.com/hwayne/awesome-cold-showers#static-vs-
| dyn.... I would probably add a caveat to the listed
| caveats that testing might not be considered? Like if
| every dynamically typed code base implements an ad-hoc
| typechecker with a testing framework, it's a distinction
| without a difference.
| laserlight wrote:
| I've passed wrong type of arguments to Python functions
| and assumed type of the return value wrong countless of
| times. That has never happened in Haskell. I don't know
| how to reconcile my experience with the statement that
| there's no evidence that strong typing reduces bugs.
| camgunz wrote:
| I program primarily in (untyped) Python (which, I get is
| technically strongly typed but people don't think in
| technical terms) these days, and I almost never
| experience type errors. I guess I could attribute this to
| a couple things:
|
| - I'm very specific about when I use None
|
| - I'm a big fan of named function arguments
|
| - I like to think my naming of things is pretty good, as
| are my conventions for parameters
|
| - I try to handle all possible cases (what I mean here is
| I do and if I don't I made a mistake)
|
| I use tests very sparingly in personal projects, but yet
| I haven't really felt their absence. If I ever write a
| piece of particularly hairy code (metaprogramming comes
| to mind... lord) I'll write a quick script testing some
| cases and then delete it.
|
| Anyway, all that is to say I think part of dynamic
| programming is you build an immune system for this stuff.
| pharmakom wrote:
| I think the big win is actually immutable data and pure
| functions. It just so happens that strong typing tends to
| come along with these things (e.g. Haskell, OCaml)
| tgflynn wrote:
| This is a very specific question but I thought I'd ask it here on
| the chance someone might have a good answer.
|
| I've been slowly working my way through this course and I ran
| into an issue recently when I upgraded my OCaml installation to
| the latest version (4.13.1). With this version I find that the
| simple instructions for building an executable with dune
| (especially one that uses OUnit) given here
| https://cs3110.github.io/textbook/chapters/data/ounit.html no
| longer work.
|
| It seems dune now requires you to initialize a project. Does
| anyone know how to translate the instructions in the course to
| the current version of dune ?
|
| I realize I could install an opam switch for the previous version
| but I'd rather be working with the latest one.
|
| Just hoping someone might be able to save me an hour or so
| figuring this out on my own.
| mc10 wrote:
| FYI: If you have more questions, the OCaml forum is very
| friendly and helpful: https://discuss.ocaml.org/
| octachron wrote:
| You can add a dune-project file at the root of your project
| with just `(lang dune 3.2)` (or your dune version rather than
| 3.2). The file can also be generated by "dune init project
| project_name" for fresh projects.
| tgflynn wrote:
| Thanks. It looks like all I needed was the project file. I
| was confused because when I followed the instructions to run
| dune init it created an entire directory structure and then I
| was confused about where my code should go (I haven't gotten
| to the chapter on modules yet).
| shikoba wrote:
| The latest version is 4.14.0. Personally I write my Makefile
| myself and use them to build my projects. It works perfectly.
| christophilus wrote:
| I love OCaml. Fast builds, native binaries, fairly expressive and
| elegant once you're accustomed to it. Now that it is multi-core,
| I hope it gains traction.
| raverbashing wrote:
| "beautiful"
|
| But then it has quirks like using ;; to end statements (EDIT:
| only in the REPL). And comments with that weird syntax
|
| But worse of all are the optional parenthesis in function calls.
| Yes, I know Ruby has it. But it feel super weird and a needless
| flexibility (that causes more confusion than it solves).
|
| I can't get over this stuff, sorry.
| toolslive wrote:
| > worst of all are the optional parenthesis in function calls
|
| ?? > let r = some_function x y z in ...
|
| There are _no_ parentheses here, and it 's not optional. What
| you probably are missing is that if `some_function` takes 3
| arguments, then `some_function x y` is a function value that
| takes 1 parameter (currying).
|
| Btw, iirc, SML doesn't have this: there you only have 1
| parameter but it could be a tuple
| frou_dh wrote:
| > Btw, iirc, SML doesn't have this: there you only have 1
| parameter but it could be a tuple
|
| Nah, SML does also support automatically curried function
| definitions, it's just that by convention that feature didn't
| get used as often. I tried to find out why that was the case
| a while ago, and stumbled on this explanation: https://www.re
| ddit.com/r/ProgrammingLanguages/comments/jde9x...
| raverbashing wrote:
| > Note how OCaml is flexible about whether you write the
| parentheses or not, and whether you write whitespace or not.
|
| from here https://cs3110.github.io/textbook/chapters/basics/t
| oplevel.h...
| toolslive wrote:
| isn't that the case in most programming languages ? In
| essence, if you have an expression _exp_ then _(exp)_ is
| also an expression. So nothing special about OCaml in that
| regard. [note, just checked python, php, and js and it is
| the case]
| raverbashing wrote:
| Ok so maybe the example is confusing, because it seemed
| it was talking about function calls (where they are
| mandatory in the languages you mentioned)
| jon_smark wrote:
| I have to interject here for the sake of those unfamiliar with
| OCaml and who may take the parent comment at face value.
|
| Saying "it has quirks like using ;; to end statements" is
| misleading to the point of just being bogus. The double semi-
| colon is only ever used in the REPL. In fact, I've been
| programming OCaml for fun and professionally for over 15 years,
| and I've never used a double semi-colon in my code, nor have I
| ever encountered one in the "wild".
| raverbashing wrote:
| Thanks for clarifying
| lairv wrote:
| Every time I use recent "functional" languages (Rust, modern
| Typescript) I realise how great OCaml is
| munchler wrote:
| Have you tried F#?
| lairv wrote:
| Nope, I must say I have only used a small subset of languages
| from the FP-language-zoo, so my opinion on OCaml might be
| biased
|
| But as an example when writing Typescript, I feel so
| frustrated of not having a clean way of doing pattern-
| matching
| pjmlp wrote:
| You will have to wait for JavaScript to add it first.
|
| The whole point of Typescript and why it is so successful,
| is because they only add type system on top of JavaScript.
|
| Pattern matching would introduce new language constructs
| beyond what is required for defining types.
| sn9 wrote:
| Typescript gets compiled to Javascript anyway. What's
| stopping any pattern matching construct from being
| compiled to vanilla JS?
|
| We already get stuff like type narrowing and generics.
| pjmlp wrote:
| The philosophy of not adding language constructs beyond
| what is required for the type system.
|
| Typescript code without type annotations should be
| JavaScript compatible, minus features still in flight for
| standardisation.
|
| There are other languages for that like ReasonML.
| jcelerier wrote:
| I did a lot of my PhD work in OCaml. I hated pretty much every
| minute of it compared to C++, when comparing the exact same
| algorithms aha.
| immigrantheart wrote:
| Oh wow, interesting. Could you elaborate why?
| muglug wrote:
| 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.
| wyager wrote:
| There 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.
| abathologist wrote:
| Haskell is a great language but has plenty of its own warts.
| The value prop and trade offs between OCaml and Haskell
| really makes it hard to use one as a drop in for the other
| imo.
| wyager wrote:
| Strong disagree; I have thousands of hours with both and I
| would essentially never recommend ocaml over haskell unless
| your company already has an ocaml codebase/ocaml expert
| employees.
|
| I'm very conscious of the existence of pareto tradeoffs; I
| am asserting that, in this case, there is essentially no
| tradeoff to be made. Haskell is equal or better (sometimes
| significantly so) in almost every relevant domain. For
| domains in which ocaml is a better choice, it is not the
| globally best choice (i.e. both ocaml and haskell are bad
| in those domains).
| substation13 wrote:
| What about the performance challenges caused by lazy
| evaluation?
| belmont_sup wrote:
| Fresh in both languages. But on studying the Effective
| Haskell book, there's a whole chapter (chp 14) on
| learning how to read and write efficient and faster
| Haskell code. Training that mental model of understanding
| how IO gets evaluated didn't seem too difficult. It feels
| akin to remembering how to write fast SQL code - you just
| practice a bit and measure.
|
| In chp 7 on understanding IO, there's also another great
| section that explains how IO evaluation is often
| confused. I share an example from the book that reads,
| writes, and prints files. Memory
| intensive func because it attempts to read _all_ files at
| once because the execution of reading and writing
| actually only happens when the print fn is called
| (putStrLn files). makeAndReadFile is actually doing both
| reading and writing - a normal task in all other
| languages. slow = let files =
| mapM makeAndReadFile [1..500] :: IO [ String ]
| in files >>= (putStrLn . show)
| Efficent version. Here we force reads and writes to
| actually occur per file instead of waiting til the print.
| Seems like a pretty easy step to misunderstand.
| safe :: IO () safe = foldl ( \ io id ->
| io >> makeAndShow id ) (return ()) [1..500]
| marcosdumay wrote:
| Most people don't even get to see those challenges. But
| make wide use of the many performance opportunities
| created by lazy evaluation. Anyway, when any problem
| appears, it is obvious, so if it was a large problem, it
| would be fixed by now. The only reason people keep
| talking about it is because it nearly never appears.
|
| Really, lazy IO is a much larger source of problems, and
| even there, after a week or two writing IO people just
| learn to write code that doesn't break due to it.
| wyager wrote:
| People fixate on this, and all I can say is
|
| * it has never been an issue for me
|
| * I don't think the performance implications are nearly
| as hard to understand as often implied
|
| * in the extreme you can just enable the STRICT language
| pragma in your project and forget about it :)
| abathologist wrote:
| That's funny. This has been a problem for every
| experienced Haskell programmer I've known who works in
| Haskell professionally. I wonder what domain you work in
| where performance and space leaks haven't been a concern?
| wyager wrote:
| It's not that space leaks are not a concern; it's that
| it's actually quite easy to avoid creating them, and if
| you manage to do so and it becomes a problem, it's
| usually easy to track down. I don't know any experienced
| haskell programmer who would even mention this in a list
| of complaints about the language (which they are sure to
| have, but they will likely be more abstract).
| yawaramin wrote:
| Compile times and architecture astronomy are obvious
| counterarguments to the 'Haskell is always superior'
| delusion ;-)
| abathologist wrote:
| >Haskell is equal or better (sometimes significantly so)
| in almost every relevant domain. For domains in which
| ocaml is a better choice, it is not the globally best
| choice (i.e. both ocaml and haskell are bad in those
| domains).
|
| I think this assertion is false, but it would take a lot
| of careful evaluation to prove it one way or the other.
| The most salient counterexample that comes to mind is
| modularity and namespacing.
|
| Of course it's possible I'm wrong, and Haskell is
| objectively superior in every regard. But the OCaml
| language and ecosystem has [a decades long track record
| of stable and evident success
| stories](https://ocaml.org/industrial-users) and I find
| it well suited for the kinds of problems I like to
| tackle. I also like the prevailing vibe in the (still)
| small community.
|
| This exchange reminds of a difference I've observed in
| the cultural tendencies in the OCaml and Haskell
| ecosystems: in my experience, OCamlers tend to not be
| very invested in arguing for the supremacy of OCaml.
| wyager wrote:
| > but it would take a lot of careful evaluation
|
| This is what I was doing for a number of years.
|
| > The most salient counterexample that comes to mind is
| modularity and namespacing.
|
| Polymorphic variants and ocaml namespacing are nice,
| granted. These are the two ocaml features I've ever
| missed while using haskell. Minor details overall though.
| Namespacing is not as useful with typeclasses and PVs
| have typing problems.
|
| > in my experience, OCamlers tend to not be very invested
| in arguing for the supremacy of OCaml
|
| Yeah, this is mostly because it's not as much of a
| marginal improvement, so it doesn't have as many people
| interested in shilling it.
| octachron wrote:
| Parametric 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 wrote:
| That'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 wrote:
| OCaml 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 wrote:
| > 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.
| octachron wrote:
| Do you have any examples? Unfortunately from my
| perspective, I cannot make sense of your statements.
|
| OCaml typing is principal in all situation where type
| inference is decidable. The syntax for annotation for
| polymorphic functions is isomorphic between OCaml and
| Haskell.
|
| If I take a random module in the standard library, let's
| say Array, 95% of the functions in this module are
| parametric polymorphic. I am thus genuinely puzzled by
| your statement that "OCaml programmers almost never write
| polymorphic functions".
| wyager wrote:
| I wrote up a bunch of grievance examples for a blog post
| I never got around to publishing, but I'm traveling at
| the moment so can't pull up my laptop. I'll see if I can
| find them later.
|
| > The syntax for annotation for polymorphic functions is
| isomorphic between OCaml and Haskell.
|
| Sorry, but total bullshit. I had to use these pieces of
| shit all the time.
| https://v2.ocaml.org/manual/locallyabstract.html
|
| > If I take a random module in the standard library,
| let's say Array
|
| As I _specifically_ called out, all the core data
| structures are polymorphic. (Let 's not talk about the
| float array hack; is that still around?)
|
| It's so annoying that pretty much every other library is
| monomorphic or functorized.
|
| > I am thus genuinely puzzled by your statement that
| "OCaml programmers almost never write polymorphic
| functions".
|
| Not sure what to tell you man. I spent 4 years reading &
| writing ocaml and 95% of the code that would have been
| polymorphic in haskell (because it would be easy/free)
| was either monomorphic or (multiple layers of) functors.
| Many/most of the devs I worked with (great devs with
| years of OCaml experience) didn't even know what LATs
| were, let alone used them, which means they were almost
| certainly not writing polymorphic code which "did
| anything" with the type. The only polymorphic code that
| you can write in ocaml without such things are functorial
| (in the categorical sense, not the ocaml sense). Arrays,
| map values, that's about it.
| octachron wrote:
| > Sorry, but total bullshit.
|
| Ok, locally abstract types are bit weird and historical
| quirk due to the pre-existing use of type variables as
| unification type variables. But first, they only matter
| with GADTs and local modules (a notion that doesn't exist
| in Haskell). And the good syntax for polymorphic function
| with GADTs is `type a. a monoid -> a` which requires just
| one explicit quantification compared to the Haskell
| variant. So no, Haskell and OCaml type annotations are
| isomorphic.
|
| > As I specifically called out, all the core data
| structures are polymorphic.
|
| I took a totally random module from the standard library!
| Let me another random module after rolling a dice,
| Atomic. Here only 66% of the functions are polymorphics.
| Or do you want me to go to another library? Ok, let's go
| for container, and let's select another random module
| CCPair: 100% of the functions are polymorphic. Honestly,
| I struggle to understand how it is possible to conclude
| that every OCaml library is monomorphic.
|
| What do you mean by LATs? The Haskell wiki doesn't seem
| to know that term.
|
| > The only polymorphic code that you can write in ocaml
| without such things are functorial
|
| I am sorry to ask but are you confusing bounded
| polymorphism with parametric polymorphism?
| wyager wrote:
| > they only matter with GADTs and local modules
|
| Local modules (assuming this means e.g. a module passed
| in as a function argument, constrained to have one of its
| types match the type of another argument) are how you
| write non-trivial polymorphic code.
|
| > Atomic ... CCPair
|
| More functors (in haskell terminology) - code that
| doesn't do anything interesting with the type parameter.
|
| > What do you mean by LAT
|
| Locally abstract type
|
| > I am sorry to ask but are you confusing bounded
| polymorphism with parametric polymorphism?
|
| Bounded polymorphism is an application of parametric
| polymorphism. Not sure what you are asking here. Possibly
| this will clarify my line of thought: I consider
| parametrically polymorphic code without any bounds to be
| "uninteresting"/"trivial", because either the code must
| treat the polymorphic type completely opaquely (leading
| to trivial functorial [in the haskell sense] operations
| like implementing fmap), or requiring you to explicitly
| pass in all supported operations on the instantiated type
| (basically requiring you to implement bounding by hand).
|
| I think this may be a blub paradox thing, where the set
| of useful applications of PP is much more restricted in
| ocaml, to the point where ocamlers do not even consider
| what they are missing. A haskell or rust programmer would
| likely run into these semantic blocks quickly upon trying
| ocaml.
| octachron wrote:
| Ok, you are using "parametric polymorphism" to mean
| bounded polymorphism. This explain my confusion! Indeed,
| I can understand your point of view then: bounded
| polymorphism is indeed better done with functors in
| OCaml. And since functor are syntactically heavy in OCaml
| there are not used everywhere and only when they are
| useful. Which doesn't mean that they are not used at all,
| as the Mirage project can attest. MirageOS is essentially
| an Operating System build upon OCaml functors.
| wyager wrote:
| I don't really consider those separate things. Bounded
| polymorphism _is_ parametric polymorphism, plus a type
| subset relationship. A quick sanity check on wikipedia is
| consistent with this model. BP is PP plus what 's
| required to make it more than a toy
|
| I remember one annoyance I had with ocaml was the
| effective inability to use point-free style. I can't
| remember what limitation was behind this; do you know off
| the top of your head?
|
| Edit: it's the "value restriction", more annoying
| bullshit I forgot about! Huge limitation for polymorphic
| ocaml code.
| substation13 wrote:
| I 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.
| octachron wrote:
| Do 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 wrote:
| yup, specific error message is here:
| https://news.ycombinator.com/item?id=31861450
| abathologist wrote:
| Looks 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 wrote:
| > 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 wrote:
| The 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 wrote:
| I 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 wrote:
| Rust 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 wrote:
| I 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 wrote:
| Looks similar to what TypeScript will offer you.
| munchler wrote:
| FWIW, 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 wrote:
| yes, 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.
| acchow wrote:
| Stockholm syndrome arrives quite quick in this case, as you
| become accustomed to this form as meaning "you're missing
| an arg"
|
| I agree the first 6 months of this is mind-numbingly
| frustrating.
| marcosdumay wrote:
| This is not Stockholm syndrome. It's just learning a
| pattern.
|
| That's one of the top few specialties tasks for humans.
| After you learn it, you immediately know its meaning, it
| becomes easy with no thinking at all required.
|
| Anyway, the plain English example from Elm on the sibling
| comment is much better, because it's already easy to
| understand before you learn to recognize it.
| tgflynn wrote:
| It 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.
| whimsicalism wrote:
| > you passed it a function that returns a list instead
|
| I don't know OCaml really, but I would read that
| intuitively as a list of (functions that return
| Result_set.t), which is not compatible with a list of
| Result_set.t.
| imajoredinecon wrote:
| Tiny 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 wrote:
| No, 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.
| thingification wrote:
| Can you explain (rather than state) what readability
| issue you think exists here?
|
| I don't know if you see a real readability issue or not,
| but I do think it's easy to jump to "hard to read" when
| that's only true in a certain parochial sense: it's not
| what we're used to.
| nh2 wrote:
| For comparison, in Haskell you would get for this
| definition: myfun :: Int -> Char -> Bool
| myfun a b = undefined
|
| if you give too many arguments: myfun 1
| 'c' 3 * Couldn't match expected type 't0 -> t'
| with actual type 'Bool' * The function 'myfun' is
| applied to three value arguments, but its type
| 'p10 -> Char -> Bool' has only two
|
| if you give too few arguments: putStrLn
| (myfun 1) * No instance for (Show (p20 ->
| Bool)) arising from a use of 'print'
| (maybe you haven't applied a function to enough arguments?)
|
| I like how it says applied to three value
| arguments ... but its type ... has only two
|
| and maybe you haven't applied a function
| to enough arguments?
| earth_walker wrote:
| Elm's developers have been working hard to make better
| error messages for these cases, and it really pays off
| even when experienced. Here are Elm's versions for
| comparison: > myfun 1 'c' 3 --
| TOO MANY ARGS -------------------------------------------
| --------------- REPL The `myfun` function
| expects 2 arguments, but it got 3 instead. 5|
| myfun 1 'c' 3 ^^^^^ Are there any
| missing commas? Or missing parentheses? > not
| (myfun 1) -- TYPE MISMATCH -------------------
| --------------------------------------- REPL
| The 1st argument to `not` is not what I expect:
| 5| not (myfun 1) ^^^^^^^
| This `myfun` call produces: a -> Bool
| But `not` needs the 1st argument to be:
| Bool
|
| Besides being 'beginner friendly', this just takes away
| cognitive overhead when you can glance at an error
| message and immediately know what happened.
|
| (Edit: Formatting. Note that in the console output the
| arrows actually line up with the source of the errors)
| [deleted]
| whimsicalism wrote:
| This seems exactly the same to me just with more
| complicated type names.
| abathologist wrote:
| Indeed it is :)
|
| Dropping an example in TryOcaml[0]: let
| f : int -> int = fun _ -> 1 ;; val f : int -> int
| = <fun> let ex : int list = [f] ;; Line
| 1, characters 21-22: Error: This expression has
| type int -> int but an expression was expected of type
| int
|
| [0]: https://try.ocamlpro.com/
| [deleted]
| octachron wrote:
| With OCaml type system permanently burned in my mind, I
| parse that error message as "there is an ` _ ->
| Result_set.t ` arrow type which is not compatible with type
| `Result_set.t`". Do you think that the issue is that the
| arrow `->` is too hard to spot?
| muglug wrote:
| Yes, I think that's the issue.
|
| I built a reasonably popular typechecker for PHP, so I
| have some experience with the DX for these sorts of
| programs. I don't think it's good for a couple of ASCII
| characters and line breaks to be the difference between
| an acceptable and an unacceptable type annotation. It
| requires, as you say, for the OCaml type system to be
| burned into people's minds for them to immediately spot
| the issue.
|
| In a similar context Rust will just say "function
| expected 5 arguments, you gave it 4" which has a very
| obvious remediation.
| thingification wrote:
| I think it would be great to address this as you
| describe, but at the same time as somebody who has
| dabbled in OCaml but is still very wet behind the ears, I
| don't understand how this error format requires the type
| system to be "burned into your mind".
| thingification wrote:
| I have no insight into what is hard and what is easy to
| implement here, but might it be feasible to break up the
| error into parts, the first just as you wrote? Along the
| lines of:
|
| 1. Error: This expression has type ('x -> Result_set.t)
| list but an expression was expected of type Result_set.t
| list
|
| 2. where 'x = (locl_ty * Tast.pos * ('ex, 'fb, 'en)
| Aast.expr_) list
|
| The solution suggested by the parent of your post sounds
| desirable also, but perhaps the currying semantics of
| Ocaml makes that difficult or poorly-defined?
| octachron wrote:
| Emphasizing the arrow (or in fact the most narrow error)
| is definitively a good idea in that case. Counting the
| number of arguments is in general a bit problematic: it
| is possible to end with an accidental functional value
| due to an extra argument send to a sufficiently
| polymorphic function for instance.
| ta-ocaml_typerr wrote:
| int_19h wrote:
| A 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.
| rwmj wrote:
| We 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.
| tgflynn wrote:
| From 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).
| shadowgovt wrote:
| This 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.
| kopecs wrote:
| Sadly 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]
| eckza wrote:
| If 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.
| kupopuffs wrote:
| Forgive my ignorance, I'd would like an example of the
| "functional" (hehe) benefits of automatic currying
| earth_walker wrote:
| Partial 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)))
| shikoba wrote:
| > when signatures changed in an unstable dependency
|
| The problem is not OCaml here.
| muglug wrote:
| I 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 wrote:
| I 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 wrote:
| This is old advice :-). Use the LSP server and dune and
| the error displays instantly, no further setup required.
| shikoba wrote:
| I don't get it. Is it sarcasm?
| BiteCode_dev wrote:
| Yes because we all work in perfect world under optimal
| conditions.
| 0x69420 wrote:
| very grateful that this course is free for all, and the youtube
| lectures are neat too
|
| real world ocaml 2e is nice, but like a lot of oreilly books
| about $LANGUAGE lately it's a lot of thinly-veiled $COMPANY
| opinions on $LANGUAGE best practices, where $COMPANY is, in this
| case, jane street. this is great if your motivation for learning
| ocaml is applying for a job at jane street
|
| if you think ocaml seems cool because wow jane street does epic
| hft in ocaml, then read real world ocaml 2e
|
| if you think ocaml seems cool because wow they wrote
| coq/fstar/the early rust compiler in ocaml, then read cs3110
| belmont_sup wrote:
| Another day, another surprisingly large discussion around OCaml
| whenever it comes up on hn.
| omginternets wrote:
| Forgive this tangential question, but does anyone know of a
| practically-oriented tutorial for implementing an ML-style
| language? Any format is fine, as are books.
| hahnbee wrote:
| Cornell alum here who took CS 3110. This course made me a better
| programmer, made me gain a sense of respect for OCaml and
| functional languages in general, and made me fall in love with
| CS. The textbook and professor are both phenomenal.
| frankohn wrote:
| As a former OCaml hobbyist programmer my take on these kind of
| books or articles is that, yes OCaml is extremely elegant and
| beautiful as a programming language and it shines for simple
| applications. Some parts of it are actually not so elegant, for
| example the object oriented aspect completely spoil the elegance
| of the core language.
|
| On the other side OCaml, as a pure functional programming
| language with immutable value by default doesn't scale well to
| large, complex application. Just the paradigm is no longer
| tenable and you need to switch at least partially to imperative
| programming with mutable variable. For example this is what it
| does the implementation of the OCaml itself.
|
| To develop further the point about "what doesn't scale" there is
| also the function with unnamed arguments and currying. While
| extremely elegant for simple programs it gets confusing for real-
| world applications when function needs quite a lot of arguments
| and there is no longer any obvious order to give them. If you
| stick with that and you choose an order it becomes arbitrary,
| difficult to remember and currying no longer makes a lot of
| sense.
|
| Functional programming with immutable values is a wrong pattern
| for programming languages. Many algorithms, almost all actually,
| are naturally expressed in imperative style with mutable arrays
| or variables.
|
| What is needed is to bridge the good things from OCaml, the type
| system, the pattern matching with tagged types into a modern,
| imperative programming languages.
|
| Rust is a sort of answer but they got it wrong because it is too
| low level about managing the memory, the ownership pardon, and
| everything else so programmers cannot just express the algorithm
| or the logic they want to implement but they have to spend a lot
| of mental energy thinking about ownership issues and unneeded
| accidental complexity like lifetime annotations.
| baby wrote:
| Btw you can actually write loops and mutate things with the ref
| keyword. It's usually much clearer than writing a loop
| recursively but not everyone uses it.
|
| The impossibility to return early in a function does create
| really convoluted code though. I'm wondering if there's a
| solution to that in FL
| cdaringe wrote:
| I'm with octaron on this one. No disrespect, but a bit of hand
| waving and big claims.
|
| I'm kindly invoking Hitchens razor, here
| jimbokun wrote:
| > Many algorithms, almost all actually, are naturally expressed
| in imperative style with mutable arrays or variables.
|
| https://www.goodreads.com/en/book/show/594288.Purely_Functio...
| yuppiemephisto wrote:
| Have you read the book and implemented its algorithms?
| kn8 wrote:
| > What is needed is to bridge the good things from OCaml, the
| type system, the pattern matching with tagged types into a
| modern, imperative programming languages.
|
| Like https://rescript-lang.org/?
| substation13 wrote:
| > Functional programming with immutable values is a wrong
| pattern for programming languages. Many algorithms, almost all
| actually, are naturally expressed in imperative style with
| mutable arrays or variables.
|
| The optimal implementation might be imperative but that doesn't
| mean we need to define our code that way. SQL is a good example
| here.
| agentultra wrote:
| > The optimal implementation might be imperative
|
| There are times when it isn't.
|
| I'm thinking of Richard Bird's functional pearl, _The
| Smallest Free Number_ , where the divide-and-conquer
| algorithm is _faster_ than the imperative one.
|
| From the conclusion:
|
| _One of the differences between a pure functional algorithm
| designer and a procedural one is that the former does not
| assume the existence of arrays with a constant-time update
| operation, at least not without a certain amount of plumbing.
| For a pure functional programmer, an update operation takes
| logarithmic time in the size of the array.1 That explains why
| there sometimes seems to be a logarithmic gap between the
| best functional and procedural solutions to a problem. But
| sometimes, as here, the gap vanishes on a closer inspection._
|
| The ability to arrive at the divide-and-conquer algorithm is
| quite fascinating as it uses plain, boring old mathematics
| and is, for some, quite straight-forward to derive on one's
| own.
|
| Yet people are more convinced by imperative implementations.
| I'm curious why this is. Is it because we "teach" people to
| believe programs are executed sequentially and are therefore
| somehow "inherently" imperative? Or is it easier to reason
| about algorithms in terms of their operational semantics?
| zasdffaa wrote:
| > Yet people are more convinced by imperative
| implementations
|
| I like and use functional and immutable but saying that's
| best is like saying a screwdriver is better. Sometimes
| imperative is cleaner. In a video, the creator of Scala, M.
| Odersky, said Scala allows mutability because sometimes it
| is cleaner.
|
| As an SQL guy, I remember one time a cursor solution was
| cleanest and best.
|
| Scala's librares have some string stuff which looks
| immutable but isn't, under the hood.
|
| As ever, it depends.
|
| > Or is it easier to reason about algorithms in terms of
| their operational semantics?
|
| Good question. I'd say people think in terms of actions not
| mathematical functions. I certainly do. Imagine teaching an
| 8-year old. And it does map so well onto the underlying
| reality of fundamentally mutable hardware.
| agentultra wrote:
| > And it does map so well onto the underlying reality of
| fundamentally mutable hardware.
|
| It maps well to the abstraction provided by the hardware.
| In reality we know that modern architectures are
| executing instructions out-of-order for efficiencies'
| sake and that data-accesses are not necessarily
| sequential either. And we haven't even considered
| multiple-core processors that are so common these days!
|
| As Bird points out in his book, when one looks close
| enough there are cases where the logarithmic gap between
| the imperative vs. functional approach disappears.
|
| And further, with register targeting and stuff it's not
| often true that recursive calls are less efficient than
| their imperative counterparts anymore... although I
| suppose historically this could be why, at least for
| small arrays, imperative programmers could assume
| constant-time indexing and updating.
|
| I'm not nearly as versed in functional programming as I'd
| like to be. I was raised on C and algorithms were always
| described in terms of procedures to me where the proofs
| took quite a bit of work to follow. However as I learn
| more about algorithm design in functional programming and
| functional data structures I can't help but notice that
| the proofs are much easier to follow while sometimes the
| solution seems quite alien and I wonder if that's because
| of my heritage of thinking operationally rather than by
| calculation. I've often been surprised to learn that many
| of my assumptions about the logarithmic complexity of
| functional algorithms are not always there!
| substation13 wrote:
| Ideally I would write my code in a declarative way and the
| compiler would figure it out. Second to that, the language
| can allow interior mutability so I can optimize.
| throwaway17_17 wrote:
| I tend to believe that there is an issue of map/territory
| confusion in teaching 'programming' and in general thinking
| about programming. Programs are usually envisioned as being
| statements which are performed in a sequence one after the
| other. This is comparable to the nature intuition about
| what an algorithm is, i.e. a series of steps to achieve a
| result when given some input.
|
| The problem then lies in the deeper explanation. It is said
| that the hardware is just taking instructions and executing
| them one after another, and that is then mapped to the
| execution of programming language statements. The issue of
| 'imperative' vs 'non-imperative' implementations is really
| a question of granularity. The map between machine
| instructions and language constructs is so large (for most
| every modern machine) that while the imperative algorithm
| seems more reflective of the underlying architecture it is
| just a layer of cover up to make programmers feel better.
|
| I really think that the appearance of control over fine
| grained instructional behavior give people the feeling that
| their imperative code is truer/more-correct in relation to
| the machine. This feeling leads to the presumption that
| imperative is more efficient.
|
| As a final caveat, the ability for a non-imperative
| algorithm to be equivalent to its imperative alternative
| tends to rely on the language's compiler (and the
| restrictions inherent in the non-imperative language). This
| is the origin of the 'with-a-smart-enough-compiler'
| argument that people sometimes level against non-imperative
| programming, i.e. there is not a compiler smart enough to
| take your high level language and produce the same machine
| code I do in my low level language.
|
| So I'm sum, I think it is both a teaching error (the
| continual re-enforcing of confusion between what a program
| says vs what a machine does) and a general human level
| confusion as to what an algorithm is at the level of
| silicon in 2022.
| int_19h wrote:
| Ironically, I find the OOP part of OCaml to be the most
| interesting, seeing how it manages to provide a self-consistent
| and powerful structurally typed object model that is no less
| powerful than what you get in C++, for example.
| octachron wrote:
| Concerning the issue with function arguments, this is one of
| the reason why OCaml has labelled arguments. And for instance,
| Janestreet's idiom udes labelled arguments as often as
| possible.
|
| Similarly, I am not sure what is the issue with using
| imperative OCaml for imperative algorithms when they are a
| better fit for the problem at hand?
| frankohn wrote:
| > And for instance, Janestreet's idiom udes labelled
| arguments as often as possible.
|
| So you have to give up to one of pillars of the functional
| programming paradigm and you get a less elegant but more
| practical programming language. Otherwise I agree that using
| labeled arguments is mostly fine and doesn't completely spoil
| the language.
|
| The more serious compromise to the functional programming
| paradigm is the fact that you need to use mutable variables
| and imperative style programming. Once you do this you lose
| most of the elegance and attractiveness of functional
| programming.
|
| > Similarly, I am not sure what is the issue with using
| imperative OCaml for imperative algorithms when they are a
| better fit for the problem at hand?
|
| What I mean is that for _any_ moderately complex application
| you need to switch to imperative style so the appeal of OCaml
| is mostly lost. You better choose a programming language that
| is designed for imperative programming since the beginning.
|
| The arguments I am giving explains why there are practically
| no real world applications done in OCaml. Some people insist
| using OCaml because they love the elegance of the language
| and I understand them but reality is it doesn't scale to
| complex applications.
|
| For people in Janestreet I think this is a sort of niche
| where they get an added value from OCaml thanks to its
| superior typing system and compile-time detection of many
| errors. I guess they care really a _lot_ about the business
| logic of their applications and OCaml shines to ensure it is
| correct so for them the advantages out-weights the
| inconveniences.
| octachron wrote:
| I am not sure which pillar of functional programming is
| lost with labelled arguments? Partial applications still
| work, higher-order function too. One might need to use
| anonymous functions when labels does not match but I don't
| see any pillar being lost.
|
| In the same way, it is perfectly possible to switch to the
| imperative style only in the specific code path where
| performance really matters and use abstraction to isolate
| this performance-sensitive part from the rest of your
| application. Ideally, you can then keep both the elegance
| of functional programming and the performance of imperative
| programming.
| [deleted]
| [deleted]
| throwaway894345 wrote:
| I generally agree, and I'm hoping that Gleam fits the bill for
| "Rust with garbage collector" (also, when I say this, people
| leap to OCaml, but OCaml introduces a bunch of problems which
| aren't present in Rust: cryptic syntax, lower quality build
| tooling, unbounded type inference, competing "standard"
| libraries, a fairly toxic community, etc).
| kitd wrote:
| > OCaml introduces a bunch of problems which aren't present
| in Rust: cryptic syntax ...
|
| Hmm, I think "cryptic syntax" is probably in the eye of the
| beholder
| throwaway894345 wrote:
| I'm sure there's some element of subjectivity, but at a
| minimum there's something to be said for "unfamiliar to the
| overwhelming majority of programmers". I think it's also
| very likely that OCaml's incredibly terse syntax (and terse
| naming conventions) is _objectively_ difficult to
| understand from a "how the human visual/symbolic
| processing pipeline works" perspective, but I don't have
| the data to back that up (I also don't think OCaml is alone
| in this regard).
| int_19h wrote:
| Thing is, if OCaml is terse and weird syntactically (and
| I would agree), then so is Rust. Especially once you have
| to spell out signatures.
| zumu wrote:
| > To develop further the point about "what doesn't scale" there
| is also the function with unnamed arguments and currying.
|
| Not sure what you mean by unnamed arguments, but criticizing
| automatic currying is totally valid.
|
| > Functional programming with immutable values is a wrong
| pattern for programming languages. Many algorithms, almost all
| actually, are naturally expressed in imperative style with
| mutable arrays or variables.
|
| This is a huge jump. Any imperative solution can just be
| expressed as a fold of some sort and many algorithms, esp.
| those that use stacks, are easily expressed recursively. Which
| one is more "natural" is 100% subjective, but I'm in the
| declarative is easier to reason about than imperative camp.
| Moreover, the idea that "unnatural" algorithm expression
| implies "doesn't scale" needs much more elaboration.
|
| > What is needed is to bridge the good things from OCaml, the
| type system, the pattern matching with tagged types into a
| modern, imperative programming languages.
|
| > Rust is a sort of answer but they got it wrong because it is
| too low level about managing the memory, the ownership pardon,
| and everything else so programmers cannot just express the
| algorithm or the logic they want to implement but they have to
| spend a lot of mental energy thinking about ownership issues
| and unneeded accidental complexity like lifetime annotations.
|
| It's not "wrong" just not what you want, and honestly the
| ownership model isn't that bad. The overhead amortizes somewhat
| as you get used to it. I of course agree there is room for more
| languages with ML-like type systems though!
| kaba0 wrote:
| There are two ways to improve performance of a given program.
| You either go lower level, giving more control over the
| execution and specifying what you want exactly, or you go
| _higher_ , not constraining the execution as much, allowing for
| better optimizations.
|
| For example a manual for loop will almost always be harder to
| optimize than a map. The latter gives explicit permission for
| reordering, allowing for vectorization and parallelization
| automatically.
|
| Also, I would think twice before claiming FP non-ideal for PLs
| - modern CPUs employ just as much FPism as they are considered
| imperative. OOE doesn't sound too imperative to me. And at the
| end of the way, imperative steps are just sequential state
| changes from a different perspective.
| the_duke wrote:
| > modern CPUs employ just as much FPism
|
| Do you have some references where I could learn more about
| that?
| kaba0 wrote:
| What I mean mostly are out-of-order-execution (reorder
| these instructions and return results as if they were
| executed in order - it is worthwhile because useful work
| can be done "in the background" while the CPU waits for
| memory. But this transformation is not too imperative in my
| opinion) and SIMD instructions (and in extension GPUs) are
| much more about transformations on data than a traditional
| Turing-machine - but there are no sharp boundaries anywhere
| here. It is just not smart to dismiss such a big and
| important part of CS, when it has plenty of applications.
| he0001 wrote:
| How do I know that some code is beautiful? And how will one know
| that it will always stay beautiful? Correct and efficient is one
| thing. Beautiful? This is off topic but topics like this grinds
| my gears as it's purely subjective.
___________________________________________________________________
(page generated 2022-06-24 23:00 UTC)