6 ms·
Care to elaborate on what you like about its syntax? I can imagine preferring it to Haskell or Lisp, but not thinking it's beautiful.
by pseudonom- 12y ago
Care to elaborate on what you like about its syntax? I can imagine preferring it to Haskell or Lisp, but not thinking it's beautiful.
- glifchits 12y agoLooking at a random file in the Flow source [0] (* 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. [0] https://github.com/facebook/flow/blob/master/hack/parsing/format_diff.ml#L67-L83 https://github.com/facebook/flow/blob/master/hack/parsing/fo...
- sampo 12y agoI agree that OCaml syntax is relatively nice, and very readable. But the first line let rec go content = may be problematic for an outsider who doesn't know that let and rec are keywords. It means let recursive function go(content) = ...
- edwintorok 12y agoLooks better with syntax highlighting: https://paste.debian.net/133342/ https://paste.debian.net/133342/
- deleted 12y ago[deleted]
- edwintorok 12y agoNitpick: [] is an empty list, an empty array would be [||]
- glifchits 12y agoI included the "or whatever" to cover this case :)
- dchichkov 12y agoI'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.
- dragonwriter 12y ago> 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.)
- codygman 12y agoTo this Haskeller it appears to be keeping state as well.
- dllthomas 12y agoCertainly some information is moving behind the scenes, given the "header ... ; modified ..."
- dllthomas 12y agoYeah, in Haskell I'd probably have done something like dropWhile (not . T.isPrefixOf "+++") >>> \case [] -> return () (line:lines) -> do header env line modified env 0 lines edited to add: code above assumes LambdaCase and OverloadedStrings extensions, and that you've imported Data.Text qualified as T
- sampo 12y agoAnother 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?
- hencq 12y agoOut 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.
- sampo 12y agoI 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.
- tel 12y agoTo be clear, here's the Haskell translation 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
- tel 12y agoOr, 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.
- masklinn 12y agoIs there any reason for using a case instead of start env [] = env start env (line:lines) | length line > 4 && take 3 line == "+++" = modified (header env line) 0 lines | otherwise = start env lines ? or even start env [] = env start env (line@('+':'+':'+':_:_):lines) = modified (header env line) 0 lines start env (_:lines) = start env lines ?
- tel 12y agoThe first one is basically identical. The second one I probably wouldn't do. In both cases it just comes down to style though.
- koenigdavidmj 12y agoWhen 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?
- tomp 12y agoDouble 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.
- sampo 12y agoIf 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. https://ocaml.org/learn/tutorials/structure_of_ocaml_programs.html#Usingandomittingand https://ocaml.org/learn/tutorials/structure_of_ocaml_program...
- mercurial 12y agoMy 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.
- pekk 12y agoA 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"
- mercurial 12y agoI 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).