After seeing that Facebook's Flow tool was written with an OCaml parser, I was quite impressed by the beauty of its syntax. It makes functional programming feel much less intimidating, compared to Haskell or a Lisp in my opinion, and I can see the parallels to F#. Thanks for sharing this introduction, it was very apropos for me!
There's one big difference, huge difference in my opinion, between Haskell and OCaml:
OCaml makes monads almost invisible. They're much more in-your-face in Haskell. I'm not sure which one I like better, but it's nice to have a plain ol' for-loop available in OCaml.
Maybe, but that's not a huge difference, because when working in a language that has monads in it, then one has to get monads (in the same sense that one has to understand iterators in other languages). There's no way around it - thankfully it isn't that complicated, too bad all of these articles flying around make it seem so.
The one humongous difference is that Haskell is non-strict and this changes everything.
I think Haskell's purity is a more humongous difference than its non-strictness, at least when you're talking about non-performance-critical applications. Laziness can produce unintuitive behavior in a program, and can make certain things harder, but purity is much more "in your face" as a programmer, and a more significant hurdle to beginners -- or at least, it was to me. I agree, though, that as the programmer becomes more sophisticated, the laziness aspect becomes a more significant difference and, ultimately, probably the biggest reason why Haskell will likely never become truly mainstream. Fortunately, there are promising languages out there which are both pure AND strict, such as Idris, Ur and Manticore.
Well, the motivation was to be pure. Non-strictness removed the temptation to extend the language in non-pure ways, either intensionally or accidentally, for the sake of convenience.
Could you explain what you mean by this? I'd say using monads is way more "in-your-face" in OCaml since you have to explicitly say which monad you're using (via opening a module or what have you) and there's no do-notation.
Perhaps you mean that one doesn't need monads as much in OCaml since it's impure and so they aren't required to manage state?
To be this is a community factor rather than a technical one. You can't talk to a Haskell person for five minutes before they're trying to explain monads to you, (often whether you already understand them or not) and to an OCaml programmer they're just another tool.
I'm fairly sure it's a complete technical one. Haskell's purity enforces effect typing, OCaml doesn't. You can just get more done without monads in OCaml and you have to have built-in syntax for sequencing (e.g. ;).
I'd rather say that the big difference is that since OCaml is impure, you're going to run into much less monads in general (though you do have monadic libraries like lwt).
That's definitely a big difference: OCaml's impurity. Everything exists in a natural (sort of, see below), invisible monad. Let binding can be both pure and impure since impure statements just return plain values upon execution. Furthermore, this is why you have to use (;) all over the place---it's really just the monadic (>>).
Which just goes to show that monads are actually totally natural. Force someone to live in one all of the time and they just forget it's there.
The major thing is that such monads don't play nicely with laziness which drove Haskell to develop (partial) purity the way that it did. This plays out in OCaml where you have to explicitly say exactly when you're forcing lazy thunks which lets the sequencing of operations remain clear.
> Which just goes to show that monads are actually totally natural. Force someone to live in one all of the time and they just forget it's there.
How is that different from saying "mutable state is natural?" I mean, if you don't see the monad, aren't we just right back at imperative programming, or is OCaml implemented with real monads under the cover that make a significant difference to the programming experience? Does it even have a basic effects system?
You can build a basic effect system in the same way you do in Haskell (except without typeclasses you need to be more explicit about which monad you're living inside of). Without syntax sugar this is a bit noisier, of course. Also, without typeclasses transformer stacks are difficult and `mtl`-style transformers are impossible (I think).
So, you can think of OCaml's semantics as living inside of a monad. Or just do it all implicitly by giving basic semantics to ;.
And the way I was speaking I mean to say that "mutable state is natural" too. I don't know that it is universally, but it's certainly something that makes 90% of programmers today feel comfortable.
Does OCaml actually need to apply special semantics to ; or do those already come with strictness? Thanks for the response BTW, this is one of those things I've always wondered about.
> And the way I was speaking I mean to say that "mutable state is natural" too. I don't know that it is universally, but it's certainly something that makes 90% of programmers today feel comfortable.
If you look through the SML definition [0] you'll see there is a semantics for declarations separated by ; specifically. It's nothing much more than sequential operation. If you had CBN then sequential effects wouldn't be, but you can define that order explicitly in CBV.
OCaml doesn't so much make monads invisible (except for a technical yet handwavy note on semicolons just as true for C) as make them unnecessary due to being more pragmatic. It's strict and allows mutability when necessary. Of course explicitly recognizing when you're using a monad is extemely powerful and allows elegant formulations of lots of wonderful things like LogicT, probabilistic computation and continuations.
I think you're munging your terminology. Side-effectful computations in haskell are done inside the IO monad. A monad is not in general a thing for side-effectful computation. They are made unnecessary for side-effectful computation in o'caml because of the inherent support for mutability.
Indeed side-effectful computation can be represented as a monad, and therefore the ; in ocaml might be interpreted as a bind. Something just needs to satisfy the monad laws in order to be a monad. However, in Haskell the IO monad is explicitly defined. This is very important. The do syntax is sugar over monadic binding them together. In ocaml, sequential computation is simply scheduled sequentially.
In haskell, something of type IO () is a VALUE which corresponds to an ACTION which can be RUN. The function which returns the IO action is still pure. It is the outside world which consumes the IO action and performs it. In o'caml, functions are merely impure as in other languages.
The function that prints 3 and returns 8 in ocaml has type "int". The function that prints 3 and returns 8 in haskell has type "IO Int"...
That is not true at all. If you have worked with any monadic data structure that is not hardcoded into the compiler (like List) for example Lwt you would see how tedious handling monads is. So tedious that not one but four syntax extensions exist to make it less boilerplate heavy (pa_lwt, lwt.ppx, pa_monad, omonad).
(* The entry point *)
let rec go content =
let env = { file = None; modified = []; line = 0; result = [] } in
let lines = split_lines content in
start env lines;
match List.rev env.result with
| [] -> []
| _ :: results -> results
(* Skip the text before the first +++ (to make things work with git show) *)
and start env = function
| [] -> ()
| line :: lines
when String.length line > 4 && String.sub line 0 3 = "+++" ->
header env line;
modified env 0 lines
| _ :: lines -> start env lines
Its beautiful to me because its very concise looking, while still using a fair bit of real English, meaningful symbols, and idioms like [] makes an empty array or whatever. I have to admit, much of this is lost on me, but it does feel more accessible than other FP languages. It almost has a Python feel.
I'm not sure it is that concise. Just look at the code that you've given. What is it actually doing? How much of it is the juicy part and how much is the language clutter? To me, it feels like in terms of the actual functionality this code does not do that much. Skips the text before first "+++". In more concise languages that whole piece would look like a single line:
env.lines = [line for line in env.lines if not line.startswith("+++")]
And just look at this:
when String.length line > 4 && String.sub line 0 3 = "+++"
Really? Specifying numbers like that in the code? And are we really going to alloc an object here? Wow. Wouldn't anyone prefer to use something like:
line.startswith("+++") ?
edit: my code doesn't implement the exact same functionality. I've only attempted to give a feel/look of the code, not rewrite it. it looks like the original code returns the lines after the '+++' lines, but I can not tell for sure, as the header/modified function implementations are not given.
> In more concise languages that whole piece would look like a single line:
---
env.lines = [line for line in env.lines if not line.startswith("+++")]
---
I don't think that's what its actually doing. I think its actually return all the lines after the first line that starts with "+++". Or maybe all those lines but the first.
(I don't think its a good example of clear FP code, either.)
I'm pretty sure the Ocaml code is doing more than the Python code you posted. Hopefully someone who knows Ocaml can explain it to us and then we can write the actual python equivalent.
The ocaml standard library is minimal to a fault. It's just enough to implement the compiler itself. For higher level operations like starts_with, you use libraries. The Batteries library for example provides what you want:
It's a problem. Until people have standardized on which 'better standard library' to use, programs will keep building from these too low level building blocks.
It is a style, not a problem. OCaml had been there a long long time. I've brushed with it a couple of times over the years, and every time the same thing.
You look at some random OCaml code, and it looks like this:
when String.length line > 4 && String.sub line 0 3 = "+++"
I'm looking at it, and I'm not even sure - is it a bug there? Should it be '>=3' or they've really wanted to say '> 4'. Numbers all other the place? Ten different variants of equal signs?
You can call it a style if that makes a difference. I think there are reasons behind that style, and I think the too minimal standard library is the main reason. The lack of a good package manager until recently may be another contributing reason.
I hesitate to answer your hyperbole about ten variants of equal signs. It's not uncommon for languages to have two notions of equality like ocaml has (or even more): Here's Python for example:
http://stackoverflow.com/questions/132988/is-there-a-differe...
To answer your original question of why anyone would prefer to not use startswith: I don't think anyone does.
start env = function
| [] -> ()
| line :: lines
when String.length line > 4 && String.sub line 0 3 = "+++" ->
header env line;
modified env 0 lines
| _ :: lines -> start env lines
is a bit elitist way to write a 2 argument function as a combination of 1 argument function and a lambda function. So let me rewrite:
start (env) (list) = match list with
| [] -> ()
| line :: lines
when String.length line > 4 && String.sub line 0 3 = "+++" ->
header env line;
modified env 0 lines
| _ :: lines -> start env lines
So the function `start` takes some kind of state `env` as a first argument, and then it takes a list as a second argument, here named `list`.
If the list is empty, [], it does nothing.
If the first list element, which is a string, starts with "+++", it calls two other functions: first header(env,line) where `line` is now the first list element, and then modified(env,0,lines) where `lines` is the rest of the list, not containing the first element.
If the first list element does not start with +++, it recurses to the rest of the list.
So this is not a filter: After first encounter of "+++" it calls those two other functions and stops.
> looks like the original code returns the lines after the '+++' lines, but I can not tell for sure, as the header/modified function implementations are not given
Actually from the line
| [] -> ()
we know that the function `start`returns nothing (well, returns a special value 'unit' which is OCaml's way to say nothing), so also the functions `header` and `modified` inside must return nothing, so they are called for their side effects only.
> we know that the function `start`returns nothing
Which is cool, but start is a (recursive) helper function for the go function (and if you look at the source file, there are a number more that are defined along with it, this is kind of a weird excerpt to just show the main go function and the first of several helpers), which is be the main public interface being defined in the code presented. I wasn't trying to describe what it looked like start did when called, but what it seemed like go was trying to do.
(And, I was doing so looking just at the excerpt and going a bit off the cuff; its clearly, looking at the source file, doing a lot more.)
Your code is not doing the same thing. A few minor bits; env has no 'lines' in its definition and String.sub does not allocate objects: it's better to think of String.sub, String.length, etc. as functions packaged in a module of helpful combinators.
OCaml is a language with currying and higher order functions, all that power is wasted if instead of composition you `.Objected` into everything. The advantages of piping, currying and composition on first class functions simply cannot be overstated.
Okay, so the next thing, from String.sub line 0 3 it's clear that you need to know the string length, hence the > 4 can't be avoided. You could pack it away into a function but why waste time if you're not going to write this 3 or more times?
The function `start` takes an env, a string list and returns unit. It's a procedure. The function looks for a string that starts with "+++" and either fails to find and returning () else passes to `modified` (also a procedure, tail recursive) which goes through the list (line :: lines returns the head and the tail of the list) applying modifications to env by tracking which lines were modified. `Lines` is an integer list and `result` is an integer pair list, so hopefully it's clearer now why your code is doing something completely different.
Another thing maybe difficult to mentally parse is the `and`. One might assume it's a logical operator, and get confused. But actually in OCaml
let rec f (farg1) =
...
and g (garg1) =
...
is used to define mutually recursive functions f and g.
Then again, in the given code the function `start`, while calling itself, doesn't call the other function `go`, so they are not really mutually recursive. `go` just calls `start` but actually `go` is not recursive at all.
So they could have written:
let rec start (env) =
...
and then
let go (content) =
...
In this case, `start` needs to be defined first in the source code, so `go` can call it. Maybe the author wanted to write `start` below the definition of `go` (kind of Haskell style) and kind of abused the mutual recursion form for stylistic reasons?
Out of interest: why is it this way? In a lot of other languages (e.g. Java or Python), you don't need to do this. Can't the compiler just figure out that a function refers to another function that's defined later in the same scope? Clojure also requires you to declare a function manually, so I guess it's not completely unheard of, but I'm curious if there's a technical reason or if it's just a more or less arbitrary choice.
I guess for historical and stylistic reasons the OCaml people wanted to allow redefining variables while using the old values in the definition.
So you can say
let process data =
let data = sanitize data in
f(data)
instead of
let process data0 =
let data1 = sanitize data0 in
f(data1)
And because OCaml uses the same let syntax for variables and functions, this also lets you to
let f x = x * x
let f x = f (f x)
# now f(3) will be 81
So we can define new f using the definition of old f.
In some cases I can see the point of overwriting variables, instead of using new names like data0, data1, data2, but in the case of functions, redefining functions while utilizing the previous definition in the body of new definition will probably not catch on.
But anyway, because this is possible, OCaml needs another way to signify the that f used in the definition of f is not meant to refer to a previous definition, but the current one.
Hence, let rec.
Also, while marking a single function recursive maybe does not make much difference when reading source code, it's kind of nice to read code when a group of mutually recursive functions is explicitly marked as such.
It's actually quite nice to have to explicitly declare when binding scopes are recursive. It's an easy bug to make on accident otherwise.
Additionally, a recursive binding (behind the scenes) is semantically much more complex as it's the effect of having a fixed-point operator called on a higher-order function. It's nice to have to make a specific syntactic call out to have this happen.
I really wish Haskell used `let rec`. It's really easy to accidentally write an infinite loop when all lets are rec, but if you forget the rec you almost certainly will get a name or type error.
Maybe I'd hate it after a day, but I think I wouldn't.
"OCaml distinguishes between nonrecursive definitions (using let) and recursive definitions (using let rec) largely for technical reasons: the type-inference algorithm needs to know when a set of function definitions are mutually recursive, and for reasons that don't apply to a pure language like Haskell, these have to be marked explicitly by the programmer."
Depends on whether /u/hencq asked about why rec is needed, or why and is needed.
I answered above, why rec. And you answered why an explicit grouping of mutually recursive functions using and is needed. And true, this is needed for type inference. And even though Standard ML does not need rec, it does need and.
Ah very interesting. I guess my question was mainly about and, though I hadn't thought about the reasons for rec. It makes sense that that removes a class of errors.
So as a follow-up question: why is type inference impossible without and or what is it about and that makes it possible? For instance, what would happen if I (out of laziness or something) would just insert and everywhere, even for functions that aren't mutually recursive. So, e.g.
let rec f x = x + 1
and g y = y * 2
Would type inference somehow fail on that? If not, what's stopping me from writing a version of OCaml where all top level lets are automatically converted to let ... and ... and? (Other than a complete lack of necessary compiler writing skills obviously)
I don't think anything stops you from defining all functions in a program in one giant let rec ... and ..., but doing that might set some other limitations on how you can structure your code.
go content =
let env = Env { file = Nothing, modified = [], line = 0, result = 0 }
lines = split_lines content
env' = start env lines
in case reverse (result env') of
[] -> []
_ : results -> results
start env x = case x of
[] -> return ()
line : lines
| length line > 4 && take 3 line == "+++" ->
do header env line
modified env 0 lines
| otherwise ->
start env lines
Or, slightly differently, start probably should have been
start env x = case x of
[] -> env
line : lines
| length line > 4 && take 3 line == "+++" ->
modified (header env line) 0 lines
| otherwise ->
start env lines
This reflects the pure functional style over the state monad mechanism I half implemented above. OCaml is just using normal mutability.
When I saw OCaml the first time I saw a much different syntax than this: lots of double semicolons (and lots more single semicolons than were present here). This looks much like Standard ML instead. What's up with that?
Double semicolons are used in the OCaml REPL to evaluate the current input (instead of newlines as in e.g. Python REPL). They don't need to be used in .ml files.
If you write in imperative style (using expressions for their side effect and discarding their return values) then you separate statements with a ;. But for code written in functional style, there will be no ;'s.
Then ;; is used to separate statements at the top level. But in most of the cases when a new top level statement begins, OCaml can understand this anyway and you can omit the ;;. Or use the ;; if you prefer that style.
So, imperative code is as full of ;'s as C code, and toplevel use of ;; is a stylistic issue.
My two favourite things are named arguments and pattern-matching with guards, along with the |> operator (though the latter could easily be implemented in Haskell).
All in all, it's a combination of features (and a lesser enthusiasm for ASCII-heavy combinators-based DSLs) which makes for code that is potentially as readable as Python.
A potential synonym for 'potentially as readable as' is
'in practice, never as readable as'. Especially when we're talking about heavy use of user-defined syntax to hide whatever's actually going to have to be maintained about this code. It's the dark side of "DSLs"
I use "potentially" because people will find a way to write unreadable Python the same way they'll write unreadable anything else. And what I was saying about OCaml is that you find less user-defined syntax than Haskell, though you do find it (eg, the LWT syntax extension, which in my experience helps rather hinders readability).
The grey boxes are the input to the REPL. You should be able to just copy paste the contents of any of the boxes. The green boxes are the output of the REPL.
You can think of '-' as meaning 'the last evaluated expression' and ':' as meaning 'has the type'. So the OCaml repl replies to your input by saying something like 'the last evaluated expression has the type ...'.
How does Ocaml do on having predictable memory usage? This is still a weak spot in Haskell, though I absolutely love the language and it hasn't mattered yet for my use cases.
The ocaml runtime is wonderfully simple (http://rwmj.wordpress.com/2009/08/04/ocaml-internals/) and the compiler focuses mostly on good codegen rather than advanced optimisations. In practice, it's common to be able to look at a block of code and predict how fast it will be and how much memory it will allocate. This is not my experience in haskell - the difference in performance when certain optimisations do or don't trigger is huge and is hard to predict just by reading the source code.
I'm very surprised articles about OCaml (and more generally functional programming) are popular on Hacker News. FP isn't exactly a new thing. I would assume most CS graduates have experience with functional programming (I learned "Caml Light" as an undergraduate in 1996).
I think -- even if they were exposed in school, and not all working programmers are CS graduates -- that many working programmers have little current experience with FP, and many lack either experience or current awareness of any particular FP language addressed in any given article (or, if they do, don't have experience with it in the context that the article is presenting.)
Such that, even though they might be distantly familiar with FP in general, its quite likely that articles on particular FP techniques, FP languages, or applications of FP to a particular problem area can still be useful.
Sometimes, it'll be useful just by reminding you of something you've been too caught up with dealing with what is immediately useful to think much about since school, but which might be more applicable to the problems you have now than the problems you had in programming work closer to the time you were in school.
As you learned Caml Light, chances are you went to a French university. As far as I understand, outside of France, OCaml/Caml Light are seldom taught at university. WRT FP, many universities will use Java or C++ to teach programming fundamentals (a terrible thing imo). Some teach or used to teach Scheme or Python.
We did a little bit of OCaml in our undergrad PL class at UCLA. If I remember correctly, it was only a couple weeks, and you're probably right in that it was a bit unusual. The less exotic classes I took mostly used C++.
But having it used in practical conditions, by companies intended to make money and writing "normal" software (i.e. not things related to formal semantics), is still really newsworthy.
And that's OCaml's appeal IMO: it focuses on things that aren't sexy to academics. Good compiler performances due to careful codegen rather than exotic theories, willingness to be "polluted" by side-effects rather than wrangling monad transformers, eager evaluation to get predictability rather than cute code...
Having a quick look at ocaml again, I find I'm hoping someone will take the time to write a "How I start"-article[1] for it, detailing things like installing (something like wget raw.githug...opam_installer.sh && ./opam_installer.sh bin - that gets you the latest opam locally), setting up janestreet core and utop, and finally bootstrapping some kind of not-entirely-trivial hello-world app and/or library for use with "opam install ...".
I did for instance find a couple of interesting (to me) libraries: https://github.com/esperco/ocaml-imap and https://github.com/nojb/ocaml-maildir . But only the first can i "opam install" -- as a newcomer to ocaml (or returnee of sorts) -- I my intuition tells me that being able to set up a chroot/image with just stuff via opam would probably be what I want.
But that's exactly why it'd be nice if someone that actually does this (and uses ocaml in anger) wrote up a proper "How I start"-article.
Is there a tutorial out there showing off the OCaml tooling from a working coder perspective? I mean setting up a project, managing dependencies, debugging, testing, etc. I really think that average joe programmers like me could benefit from features offered by strongly typed FPL, but lack of good tooling is a show stopper. Elixir does an amazing job at this, but it's not as safe and general purpose as OCaml or Haskell.
97 comments
[ 2.6 ms ] story [ 167 ms ] threadThough, that said, if you're familiar with OCaml then Haskell should be rather easy to pick up syntactically.
OCaml makes monads almost invisible. They're much more in-your-face in Haskell. I'm not sure which one I like better, but it's nice to have a plain ol' for-loop available in OCaml.
The one humongous difference is that Haskell is non-strict and this changes everything.
https://www.haskell.org/haskellwiki/Lazy_vs._non-strict
Perhaps you mean that one doesn't need monads as much in OCaml since it's impure and so they aren't required to manage state?
Which just goes to show that monads are actually totally natural. Force someone to live in one all of the time and they just forget it's there.
The major thing is that such monads don't play nicely with laziness which drove Haskell to develop (partial) purity the way that it did. This plays out in OCaml where you have to explicitly say exactly when you're forcing lazy thunks which lets the sequencing of operations remain clear.
How is that different from saying "mutable state is natural?" I mean, if you don't see the monad, aren't we just right back at imperative programming, or is OCaml implemented with real monads under the cover that make a significant difference to the programming experience? Does it even have a basic effects system?
So, you can think of OCaml's semantics as living inside of a monad. Or just do it all implicitly by giving basic semantics to ;.
And the way I was speaking I mean to say that "mutable state is natural" too. I don't know that it is universally, but it's certainly something that makes 90% of programmers today feel comfortable.
> And the way I was speaking I mean to say that "mutable state is natural" too. I don't know that it is universally, but it's certainly something that makes 90% of programmers today feel comfortable.
Well, as long as time and change are considered.
[0] http://sml-family.org/sml97-defn.pdf
Specific instances of monads are definable in most languages in use today, it's when you want to generalize over monads that Haskell stands out. For that you want higher kindedness, Ocaml can simulate them using the module system but it's rather cumbersome. There is a work around described here: https://ocamllabs.github.io/higher/lightweight-higher-kinded.... The work around is also applicable to the cousin, F#: https://github.com/palladin/Higher/tree/master/src/Higher.Co...
Indeed side-effectful computation can be represented as a monad, and therefore the ; in ocaml might be interpreted as a bind. Something just needs to satisfy the monad laws in order to be a monad. However, in Haskell the IO monad is explicitly defined. This is very important. The do syntax is sugar over monadic binding them together. In ocaml, sequential computation is simply scheduled sequentially.
In haskell, something of type IO () is a VALUE which corresponds to an ACTION which can be RUN. The function which returns the IO action is still pure. It is the outside world which consumes the IO action and performs it. In o'caml, functions are merely impure as in other languages.
The function that prints 3 and returns 8 in ocaml has type "int". The function that prints 3 and returns 8 in haskell has type "IO Int"...
That is not true at all. If you have worked with any monadic data structure that is not hardcoded into the compiler (like List) for example Lwt you would see how tedious handling monads is. So tedious that not one but four syntax extensions exist to make it less boilerplate heavy (pa_lwt, lwt.ppx, pa_monad, omonad).
[0] https://github.com/facebook/flow/blob/master/hack/parsing/fo...
It means let recursive function go(content) = ...
---
---I don't think that's what its actually doing. I think its actually return all the lines after the first line that starts with "+++". Or maybe all those lines but the first.
(I don't think its a good example of clear FP code, either.)
You look at some random OCaml code, and it looks like this:
I'm looking at it, and I'm not even sure - is it a bug there? Should it be '>=3' or they've really wanted to say '> 4'. Numbers all other the place? Ten different variants of equal signs?I hesitate to answer your hyperbole about ten variants of equal signs. It's not uncommon for languages to have two notions of equality like ocaml has (or even more): Here's Python for example: http://stackoverflow.com/questions/132988/is-there-a-differe...
To answer your original question of why anyone would prefer to not use startswith: I don't think anyone does.
If the list is empty, [], it does nothing.
If the first list element, which is a string, starts with "+++", it calls two other functions: first header(env,line) where `line` is now the first list element, and then modified(env,0,lines) where `lines` is the rest of the list, not containing the first element.
If the first list element does not start with +++, it recurses to the rest of the list.
So this is not a filter: After first encounter of "+++" it calls those two other functions and stops.
Actually from the line
we know that the function `start`returns nothing (well, returns a special value 'unit' which is OCaml's way to say nothing), so also the functions `header` and `modified` inside must return nothing, so they are called for their side effects only.Which is cool, but start is a (recursive) helper function for the go function (and if you look at the source file, there are a number more that are defined along with it, this is kind of a weird excerpt to just show the main go function and the first of several helpers), which is be the main public interface being defined in the code presented. I wasn't trying to describe what it looked like start did when called, but what it seemed like go was trying to do.
(And, I was doing so looking just at the excerpt and going a bit off the cuff; its clearly, looking at the source file, doing a lot more.)
OCaml is a language with currying and higher order functions, all that power is wasted if instead of composition you `.Objected` into everything. The advantages of piping, currying and composition on first class functions simply cannot be overstated.
Okay, so the next thing, from String.sub line 0 3 it's clear that you need to know the string length, hence the > 4 can't be avoided. You could pack it away into a function but why waste time if you're not going to write this 3 or more times?
The function `start` takes an env, a string list and returns unit. It's a procedure. The function looks for a string that starts with "+++" and either fails to find and returning () else passes to `modified` (also a procedure, tail recursive) which goes through the list (line :: lines returns the head and the tail of the list) applying modifications to env by tracking which lines were modified. `Lines` is an integer list and `result` is an integer pair list, so hopefully it's clearer now why your code is doing something completely different.
Then again, in the given code the function `start`, while calling itself, doesn't call the other function `go`, so they are not really mutually recursive. `go` just calls `start` but actually `go` is not recursive at all.
So they could have written:
and then In this case, `start` needs to be defined first in the source code, so `go` can call it. Maybe the author wanted to write `start` below the definition of `go` (kind of Haskell style) and kind of abused the mutual recursion form for stylistic reasons?So you can say
instead of And because OCaml uses the same let syntax for variables and functions, this also lets you to So we can define new f using the definition of old f.In some cases I can see the point of overwriting variables, instead of using new names like data0, data1, data2, but in the case of functions, redefining functions while utilizing the previous definition in the body of new definition will probably not catch on.
But anyway, because this is possible, OCaml needs another way to signify the that f used in the definition of f is not meant to refer to a previous definition, but the current one.
Hence, let rec.
Also, while marking a single function recursive maybe does not make much difference when reading source code, it's kind of nice to read code when a group of mutually recursive functions is explicitly marked as such.
Additionally, a recursive binding (behind the scenes) is semantically much more complex as it's the effect of having a fixed-point operator called on a higher-order function. It's nice to have to make a specific syntactic call out to have this happen.
Maybe I'd hate it after a day, but I think I wouldn't.
"OCaml distinguishes between nonrecursive definitions (using let) and recursive definitions (using let rec) largely for technical reasons: the type-inference algorithm needs to know when a set of function definitions are mutually recursive, and for reasons that don't apply to a pure language like Haskell, these have to be marked explicitly by the programmer."
Depends on whether /u/hencq asked about why rec is needed, or why and is needed.
I answered above, why rec. And you answered why an explicit grouping of mutually recursive functions using and is needed. And true, this is needed for type inference. And even though Standard ML does not need rec, it does need and.
So as a follow-up question: why is type inference impossible without and or what is it about and that makes it possible? For instance, what would happen if I (out of laziness or something) would just insert and everywhere, even for functions that aren't mutually recursive. So, e.g.
Would type inference somehow fail on that? If not, what's stopping me from writing a version of OCaml where all top level lets are automatically converted to let ... and ... and? (Other than a complete lack of necessary compiler writing skills obviously)I don't think anything stops you from defining all functions in a program in one giant let rec ... and ..., but doing that might set some other limitations on how you can structure your code.
or even
Then ;; is used to separate statements at the top level. But in most of the cases when a new top level statement begins, OCaml can understand this anyway and you can omit the ;;. Or use the ;; if you prefer that style.
So, imperative code is as full of ;'s as C code, and toplevel use of ;; is a stylistic issue.
https://ocaml.org/learn/tutorials/structure_of_ocaml_program...
All in all, it's a combination of features (and a lesser enthusiasm for ASCII-heavy combinators-based DSLs) which makes for code that is potentially as readable as Python.
- :
Is that the prompt from the OCaml REPL (does OCaml have a REPL)?
EDIT: I think I misread things - it's the output from the examples that has the "- :" prefix, so I'm guessing it is REPL output.
that's what the REPL looks like on my end
Doesn't it hurt having a GIL?
How awesome is having the ML module system?
GC pauses haven't hurt us really. We usually just spawn a ton of processes so if one process pauses a bit to GC we won't notice.
The module system is awesome. It's something i plan to cover next time I find the time to extend the article.
[1] https://realworldocaml.org/v1/en/html/pt03.html
There is ongoing work to make the runtime multicore friendly without GIL.
Plus the GIL doesn't affect when you write concurrent code, which makes use of green thread, think go-routines.
It is only an issue in Python and Ruby because they jump out to C extensions which use the GIL, while OCaml is a native compiled language.
Anyway OCaml vnext whenever comes out, might be GIL free.
Such that, even though they might be distantly familiar with FP in general, its quite likely that articles on particular FP techniques, FP languages, or applications of FP to a particular problem area can still be useful.
Sometimes, it'll be useful just by reminding you of something you've been too caught up with dealing with what is immediately useful to think much about since school, but which might be more applicable to the problems you have now than the problems you had in programming work closer to the time you were in school.
But having it used in practical conditions, by companies intended to make money and writing "normal" software (i.e. not things related to formal semantics), is still really newsworthy.
And that's OCaml's appeal IMO: it focuses on things that aren't sexy to academics. Good compiler performances due to careful codegen rather than exotic theories, willingness to be "polluted" by side-effects rather than wrangling monad transformers, eager evaluation to get predictability rather than cute code...
I did for instance find a couple of interesting (to me) libraries: https://github.com/esperco/ocaml-imap and https://github.com/nojb/ocaml-maildir . But only the first can i "opam install" -- as a newcomer to ocaml (or returnee of sorts) -- I my intuition tells me that being able to set up a chroot/image with just stuff via opam would probably be what I want.
But that's exactly why it'd be nice if someone that actually does this (and uses ocaml in anger) wrote up a proper "How I start"-article.
[1] https://howistart.org/