8 ms·
Erlang beauty
- nathancahill 11y agoExcellent heuristics for any functional language, not just Erlang.
- efnx 11y agoNot being able to pass functions as values is a show stopper - it's arguably the single most important tenet of functional languages.
- kungfooguru 11y agoYou can pass functions. The article was saying not to nest function calls.
- nathancahill 11y agoI understand her point to be about passing called functions. Surely passing functions as values (partials, etc) is fine? final_function(maybe_function(X)) vs final_function(maybe_function)(x) Disclaimer, not familiar with Erlang syntax, so the above example is psuedo-code. "If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program." --Linus
- kungfooguru 11y agoRight, you can pass a function: final_function(fun() -> maybe_function(X) end) or final_function(maybe_function, X) if final_function does: final_function(Function, X) -> Function(X).
- oinksoft 11y agoSmall thing, but `final_function` here needs to be passed `fun maybe_function/1`, not the atom `maybe_function` alone.
- kungfooguru 11y agoOops, yup. Meant to make it explicit since you can also do: ?MODULE:Function(X)
- querulous 11y agoi believe she was talking specifically about `foo(bar(baz(X)))` and not passing functions themselves as arguments
- nathancahill 11y ago*she When in doubt, 'they' is a safe bet too.
- efnx 11y agoAhh - that makes more sense. Still, this practice wouldn't make much sense for the ML family of languages.
- rdtsc 11y agoYou can't, where? You mean in general in some functional languages? Sorry I don't see how your comment fits with the gp's comment.
- dmix 11y agoI started off as a designer before learning to program and I have a minor unhealthy attachment to the design and composition of functions. I've been learning Erlang recently and I have to say Erlang's pattern matching and list comprehension make it one of my favourite languages in terms of design (I should note I'm also a fan of Scheme/Clojure style which I know many people aren't). It certainly didn't appear that way when I first came across Erlang, as it's not always readable to the uninitiated (and nearly turned me off the language) but brilliant for those that are familiar with it. Elixir has also done a good job of translating it for people who like Ruby-style syntax, I prefer the flexibility of the more verbose Erlang.
- jacquesm 11y agoErlang the language is nice, but where it really comes together is the runtime environment.
- rlander 11y agoThis! Erlang's syntax is so small and simple, it probably fits on a postcard. Although I appreciate the excellent tools and libraries built by the Elixir community, I have a hard time understanding how one can prefer Elixir over Erlang.
- rdtsc 11y agoIt is simple. I think it was one of the design goals. The other interesting thing is over the years creators have added features but have tried to also remove features to keep it is simple. That is very hard to do but they fought for it, and I think it paid off after many years.
- rlander 11y agoYup, got bitten by this design goal once, when support for parameterized modules was removed (though I do agree it was the right move in retrospect).
- brobinson 11y ago
- lukasb 11y agoWhy foo(X) -> foo1(X). foo1(bar) -> void; foo1(_Else) -> undefined. rather than just foo(bar) -> void; foo (_Else) -> undefined. ?
- mml 11y agofoo(_Else). -> undefined. Doesn't seem very erlangy. I'd expect a runtime error, as someone's passed foo() the wrong value.
- kevinwang 11y agoBut in the given example, isn't foo(_Else) = foo1(_Else) = undefined anyways?
- simoncion 11y ago> Doesn't seem very erlangy. I'd expect a runtime error, as someone's passed foo() the wrong value. It depends. Sometimes you want to crash when handed an unexpected value. Sometimes you expect that you'll be handed unexpected values and need to either fail more gracefully, print a warning and then fail, or warn and keep going, or whatever. Erlang gives you a bunch of tools, and they all have their place. [0] :) [0] With the possible exception of the if statement... which is of questionable utility.
- asabil 11y agoThe "if" keyword should have been "when" instead. It's useful in some cases, but rarely used.
- jcizzle 11y agoIt is just a simple example to convey that a portion of foo/1 contains a case statement, and that case statement is being replaced with a function. There could be more lines in foo/1. The point is that both the case statement and the multi-clause function are identical in behavior, but the function prevents nesting at the call site.
- rdtsc 11y ago
- mml 11y agoNice guidelines. Having finally put a few hundred lines of erlang under my belt, it's clear to my that it's a wildly under appreciated language.
- too_late 11y agoF.U.C.R.S Maybe those subheadings could be rearranged or...
- dragonwriter 11y ago> Someone smart, somewhere, at some time, did a study which concluded that the human brain can easily hold six or seven items in short-term memory with little trouble, but beyond that, it becomes taxing. This applies nicely to lines of code in a function. Amazingly, after all the above guidelines have been followed, bringing a function’s lines-of-code count down to a maximum of seven is surprisingly easy to do. https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus... This has got to place as one of the most misapplied research findings in history. If you have an aesthetic preference for 7 as a limit to the number of lines of code in a function, then, sure, do that, but don't pretend there is science behind it (or at least not this particular science.)
- lqdc13 11y agoPretty sure nobody did a study with a specific number of LoC for best understanding, because it probably depends on a lot of things. But quick understanding is a function of consistent style and a small number of things to keep in mind required to understand each part. Things are easier to understand when the deciphering part is done quickly and also when you only have to keep as few things in mind as possible. An example of a Python statement that is short, but difficult to understand: '\n'.join([map(some_func, a) + 'somth' for i, j in c.items() for a, b in i.items()])
- jacquesm 11y agoShe may be wrong about that particular number, but it is really no secret that reducing the scope is a key ingredient to solving problems and avoiding bugs. Another key ingredient is to stay far away from side-effects and Erlang does a fairly good job of riding that fine line between ideal theory and pragmatism. edit: gender of author corrected, thanks Nathancahill
- nathancahill 11y ago*she When in doubt, 'they' is a safe bet too.
- 11y ago
- FLGMwt 11y agoHow does everyone feel about the last item, the "seven lines per function rule"? I can definitely appreciate the readability of this assuming the functions and intermediate variables are well named, but am I the only one that fears negative PR feedback about adding cruft?
- ShaneWilton 11y ago"but am I the only one that fears negative PR feedback about adding cruft?" Go for it, and you might be surprised as how your team responds. The biggest improvements to our codebase have come from my colleagues making controversial changes. Sometimes they don't get merged, but sometimes they end up shaping how we write code going forward. You should never be scared of negative PR feedback -- your reviewers aren't judging you, they're judging your code, and code responds surprisingly well to criticism.
- simoncion 11y ago> How does everyone feel about the last item, the "seven lines per function rule"? As with all constructs that incorporate magic numbers, I really, rather dislike it. Playing "follow the function call trail" can be just as harmful to code readability as overly-long functions. I do strive to write functions that are as short as is reasonable, and I do lift single-use code blocks out into their own functions when doing so improves clarity. However, I'm neither an English 101 student, nor a newspaper journalist: I don't stress about whether my work meets some arbitrary length criteria. :)
- kybernetikos 11y agoIt's amazing how people seem to miss that sending their readers jumping around all over the place just to get an idea of what is going on can be worse than long functions where the code reads coherently and simply. I think Clean Code has encouraged people to write a lot of single line functions that get called from a single place (often with weird parameter lists) as a means of replacing commented code blocks. While it has its place, this is not a good pattern in general. Good function writing is similar to what gets said for objects in OO design. The function should do one cohesive thing that it is sensible to give a name to and consider at least somewhat in isolation from its context. It should take in a relatively small number of parameters (external dependencies for classes), all of which should be the sorts of things you'd expect it to need from its name.
- dave_ops 11y agoSeems reasonable to me... except I don't see the point of having foo/1 and foo1/1. The author could just have easily done: foo(bar) -> void; foo(X) -> undefined. without the need for the proxy function: foo(X) -> foo1(X).
- strmpnk 11y agoI think it was for demonstration. The example certainly is a bit contrived but the general idea is that pattern matching in function heads is preferred over pattern matching in case expressions by most experienced Erlang developers I know and certainly by the author. It's not a hard rule but there are definitely times when giving a name to a set of patters is better than the anonymous case. Some other languages use things like let-in and with to achieve similar ideas but Erlang is a rather simple language so it just reuses function heads instead of nesting definitions.
- im_down_w_otp 11y agoYeah, it makes for some very interesting uses cases for code-generation. I've converted entire databases of mostly static data into nothing but a bunch of exact-match function head signatures and let one of the fastest paths in the VM be my "query planner". It's insanely fast (at the expense of compile time) and the generated code is really easy to read, trace, and debug.
- fenollp 11y agoIntersting. Do you have an example somewhere?
- rdtsc 11y agoThe point is to show an example of how a mechanical translation could work from one form to another. It is like an intermediate step if you wish. You'd instead do that code snippet you wrote first of course. But yeah looking back at comments here I think they've confused others with that code as well it seems ;-)
- brianberns 11y agoThe pipe operator is extremely elegant and helpful in F# and (I presume) Elixir. I don't understand why the author considers it "malpractice".
- strmpnk 11y agoSince Erlang doesn't have a pipe operator it's hard to compare but I'd imagine using nested calls where a pipe might work is also considered inelegant in F#?
- EdwardDiego 11y agoIt's the "incredibly" that is tripping people up on that statement, I suspect. I personally love the pipe operator in F#, and use the Clojure threading macros in much the same way - it removes nesting, and it makes logic flow more obvious
- lostcolony 11y agoWell, the author says it helps 'mollify this malpractice', which I would take to mean it helps improve the readability of nesting functions. Mind you, the author says "incredibly", too, and I'm not sure how to take that.
- losvedir 11y agoHeh, FUCRS? At first skim, I thought the cheerleader chant spelled "FUNCS", which is cute for Erlang. Is this just a jokey abbreviation of "fuckers", a random set of letters given in a funny way, or am I missing something?
- lostcolony 11y agoI don't think you are, no. It might have been a backronym, or it might be a legit acronym, but either way it means nothing to me either (well, from a programming, Erlang especially, context)
- wetmore 11y agoPerhaps if they used 10 instead of 7 for the magic number, they could have used CRUFT.
- kazinator 11y agofoo(X) -> case X of bar -> void; _Else -> undefined end. > would become: foo(X) -> foo1(X). foo1(bar) -> void; foo1(_Else) -> undefined. Incomprehensible rubbish, made more obfuscated by the second form. Okay, X appears to be an identifier bound as a parameter. So then why doesn't bar behave that way in foo1(bar) -> void is it because the X symbol is upper case? Or one letter wide? (Yuck, if so.) Or does it have something to do with that semicolon? (Double yuck.) At a glance, how the heck do we know when a symbol denotes a binding, and when it denotes itself? "_Else" is beautiful? It reminds one of the "_Bool" retch, vomited up in C99 by a drunken ISO C committee; but at least that is hidden by a #define bool _Bool that everyone halfway sane uses in its place. In any event, splitting case statements into pattern matching functions isn't "lucid". Unless you're doing OOP, it's largely stupid. The case statement keeps the relevant cases together. It "en-case-apsulates", pardon the pun. The only reason that the individual functions retain clarity is that they are grouped close together, so we, the readers, can reconstruct them as the case handling aggregate that they are.
- simoncion 11y ago> Incomprehensible rubbish, made more obfuscated by the second form. Okay, X appears to be an identifier bound as a parameter. So then why doesn't bar behave that way in... It appears that you don't understand Erlang syntax. Go back and read these two chapters [0][1] of Learn You Some Erlang, and revisit the article. [0] http://learnyousomeerlang.com/starting-out-for-real http://learnyousomeerlang.com/starting-out-for-real [1] http://learnyousomeerlang.com/syntax-in-functions http://learnyousomeerlang.com/syntax-in-functions
- rdtsc 11y agoMeh, don't feed the trolls, I think it is pretty clear they don't want to learn, rather they want to spew angry rubbish into the conversation.
- kazinator 11y agoI'm grateful for the response; it clarified/confirmed things. I promise you, I will not write another word anywhere about Erlang.
- biokoda 11y agoLets play devils advocate. Does pretty == readable? I consider this: foo(X) -> case X of bar -> void; _Else -> undefined end. More readable than: foo(X) -> foo1(X). foo1(bar) -> void; foo1(_Else) -> undefined. First function is parsable in its entirety immediately. It conveys an idea in a single fragment, which is not too large and it is not too small. Foo executes depending on value of X. Second function requires parsing two fragments of code. It requires a not-so-pretty naming scheme of appending 1 to the function. And you can not grasp the entirety of the matter in a single glance. To parse this function you must follow a train of thought: foo -> foo1 -> execute depending on X. Second function style of code results in a million functions in a single module which I very much disagree results in more readable code. Moving on to next example: foo(X) -> final_function(maybe_function(X)). foo(X) -> Maybe = maybe_function(X), final_function(Maybe). I fail to see how second foo is more clear. In Erlang maybe value can be absolutely anything. It is unnecessary verbose. The explanation on what is wrong with first function contains way to many "shoulds" and not enough "becauses".
- rdtsc 11y ago> Second function requires parsing two fragments of code. foo1 is used to illustrate a point not that you'd name functions like that. The whole thing would be probably: foo(bar) -> void; foo(_) -> undefined. I don't know, I see that better than the case statement.
- biokoda 11y agoAppending 1 to functions is quite common in Erlang for cases such as that. You cant find a good naming scheme for small fragments of ideas.
- rdtsc 11y agoI don't know, I find I do it with variables more than functions. With functions I seprate them by arity, so there could be a foo/0 and foo/1 maybe. But usually I haven't seen much fun1 fun2 fun3 ... pattern. And if you don't see a way, don't split it. I mean, make your code look good and easy to understand for yourself and others, that's the point.
- technion 11y agoThe beauty of Erlang became apparent to me when looking at a the structure of Google's Certificate Transparency structures. There are C implementations with loops, gotos and memcpy()'s. In Erlang I could do this: <<Version:8, LeafType:8, Timestamp:64, LogType:16, ASNLen:24, Cert/binary>> = PemBin.