

On F# code readability - gebe
http://www.clear-lines.com/blog/post/On-F-code-readability.aspx

======
crntaylor
I disagree with his example of 'unintelligible' code. He suggests replacing

    
    
        match list with
          | x::xs -> ...
    

with

    
    
        match list with
          | head::tail -> ...
    

to make it clear what the names are referring to. But in F#, something to the
left of :: can never be anything other than the head of a list, and something
to the right of :: can never be anything other than the tail of a list!
Calling them 'head' and 'tail' doesn't add any extra information. It just adds
more characters.

The names _x_ and _xs_ are entirely appropriate when you don't know anything
about the values you're working with (not even their types). All you know is
that _x_ has a type 'a, and _xs_ has the type 'a list. Giving them abstract
names reflects the fact that you are writing an abstract function. If you are
writing a more concrete function, use more concrete names (e.g. _trade_ or
_blogPost_ or _playerAction_... but not _head_ and _tail_!)

Also, I don't know any F#, but is he correct when he says that the order of
declarations in a file matters, and that _the order of files in a project
matters_? That sounds crazy to me. What is the benefit? Both Haskell and Ocaml
get along fine without relying on declaration order to do type inference.

Other than that - good article, I agree!

~~~
watt
Oh but it does.... I don't know first thing about F#, so "x::xs" looks alien
and very unapproachable (and also will make the rest of method body
confusing).

In my mind it's that code _readability_ is at last being discussed, signals
that F# might be nearing mainstream.

~~~
sdevlin
Surely questions of readability should be decided with an eye towards
experienced users of the language and not those who "don't know the first
thing about F#".

I agree with the GP that x::xs is more appropriate.

~~~
watt
You will end up with just another fringe write-only language.

~~~
chc
I do not think that phrase means what you think it means. If the code is
readable to somebody of average skill who has learned the language, it is not
"write-only." The fact that you can't read code in a language you don't know
shouldn't surprise you any more than the fact that I can't read a book in
Vietnamese. My inability to read Vietnamese does not make it "write-only" —
it's just a sign that I haven't learned the language.

As for "fringe," that may or may not be true. It's pretty subjective. But
really, who cares? Back when Ruby was the weird new thing gaining visibility,
I heard the same complaints about its idioms. Last I checked, it's still doing
fine. I'm OK using "fringe" languages like Ruby.

------
tikhonj
The x::xs example is bad because it's a widely used idiom, similar to using i
as a loop counter.

Also, I think that short names are completely justified if the variable is
used on a single line. In fact, in those cases, the shorter name is often
clearer because it doesn't obscure the structure of the line.

So, my rule of thumb: top-level bindings, function arguments for complicated
functions and local bindings that are either used in more than one place or
away from where they are defined all get descriptive names. (Although I do try
to keep even those brief--more than two words is probably too much, and even
two is suspicious.) Function arguments to small functions and local variables
that are used immediately (usually from pattern matches) get short names--one
or two characters, usually.

Of course, this really is just a rule of thumb. If deviating from it seems to
improve my code, I deviate. But it does give a good idea of the shape of my
code.

~~~
gnaritas
> The x::xs example is bad because it's a widely used idiom, similar to using
> i as a loop counter.

And IMHO bad for the same reason. I'd rather see "index" than "i" and I'd
rather see "head::tail" than "x::xs", abbreviations for variable names are
terrible; the English language is not running out of words and readability is
more important than brevity. Of course, I'm a Smalltalker and we like to
actually have readable code.

~~~
mich41
_Everybody_ who has ever seen any C code has

    
    
      #define i index
    

programmed deeply in his brain's CPP.

Using _i_ for loop counter never causes confusion and using _index_ makes the
for statement 12 characters longer for no reason.

~~~
gnaritas
I'm sure they do, it's not relevant; using short meaningless variables names
as a matter of habit eventually always causes confusion because of nested
loops. If you can touch type, counting characters is an absurd reason to write
obtuse code. Code is read far far far more than it is written; optimize for
reading, not writing.

I'm very well are "i" is idiomatic; I still think it's stupid, as are many
such idiomatic hangovers. Idiomatic does not mean correct or good, it means
popular. Well chosen meaningful variable names are always a plus and are a
sign of a programmer that understands code is read far more than it is written
and not always by those who know all the little idioms of a particular
language.

~~~
mich41
If you count to 100 or iterate over some array, _i_ , _j_ , _k_ , _l_ are more
readable than _index1_ , _index2_ , _index3_ , _index4_ simply because they
are shorter and still mean the same.

And if you iterate over some collections of "meaningful" objects, meaningful
names are more readable than either _i_ or _index_.

It never makes sense to use _index_ over _i_.

~~~
gnaritas
If there were nested loops, I'd use meaningful names, and as a matter of
course of using meaningful names, index beats i on a single loop. It's silly
to sometimes use meaningful names and sometimes abbreviate because you're too
lazy to type.

Of course, these are personal subjective things, but if you allow the habit of
abbreviation, it tends to spread where it isn't appropriate, better to simply
not do it.

~~~
Dylan16807
'index' just isn't meaningful. Use actual meaning or use shorthand. Don't
waste verbosity.

~~~
gnaritas
"index" is very meaningful, it tells me I'm indexing and not counting,
otherwise I'd use "count". Confusion between those two often causes off by one
errors, so yes it does actually matter and is actually meaningful. You might
like "i" or "n" to convey the same meaning, but as I said, I want words not
abbreviations.

------
MichaelGG
Preferring head::tail over x::xs sorta indicates they have read little
functional code. Using x and xs (plural) is pretty common and straightforward.
head::tail doesn't help at all.

Edit: Also, even in C#, the whole "one class per file" is often unwise. So
many times I come across projects that have a bunch of bloody files to define
interfaces with a handful of members. And then a bunch of implementations that
have 2-line implementations. It's sorta ridiculous. I think it's a holdover
from Java's idiotic "tie the filesystem directly into the compilation output"
approach.

~~~
NateDad
Actually, if you're browsing the code in Visual Studio, it's a hell of a lot
easier to jump around the code if you have one class per file. All the main
navigation UI is geared to the file level - the tree in solution explorer is
all files. The tabs in the editor are lists of files. ctrl-tab brings up a
list of files. You can bring up a new tab group and see two files side by
side.

So, yes, one thing per file is actually a pretty good idea. Sometimes it's
fine to put multiple things into a single file, if there's a lot of small
implementations... but for anything even mediumly big, it's good to give it
its own file. It helps a lot with navigation.

And yes, I know you can do "go to definition" etc, but if you want to go back
and forth between code, separate files is a lot easier. It also makes source
control a lot easier, because you can see a file changed and know immediately
what it will and will not affect.

Which is not to say that I don't think java got it wrong, it did, it's just
that the problem is not with one class per file, but rather that the file
_heirarchy_ matters.

~~~
MichaelGG
In C#, VS has a class view that lets you jump around by class name. You can
split the window and compare within the same file just fine. And if anything,
you're complaining about VS's lack of capabilities.

I'm not arguing that you never want a separate file, just that having a forced
"one class per file" rule just sprays files all over and means you have to
jump around more. Java's hierarchy+class=file is just silly; there's nothing
stopping people from enforcing that themselves, if they so choose.

------
ajanuary
w.r.t. x::xs vs. head::tail

One of the nice conceptual things about pattern matching is the pattern on the
left hand side describes which bits of the structure you're interested in,
while the stuff on the right hand side describes how to manipulate that data.

When you're looking at the rhs it shouldn't care about where data came from,
it should just care about what the data is. I've got some arbitrary thing `x`
and a list of arbitrary things `xs`. I've got some `person` and a list of
`people`.

Using `head` and `tail` makes the rhs concerned with the structure of the
incoming data. This becomes more of a problem if you have more complicated
data structures on the lhs.

One of the key elements of readable code is how easy it is to map your
conceptual model onto source code onto executable code. Using head::tail
wouldn't help new people grok the separation of structure on the lhs and
manipulation on the rhs.

All that said, it's such a minor point as to probably not really have any
impact in this case. Just a bit of silly purist hand-waving.

~~~
ufo
I really like this analogy. I think a good example of it is the following code
for red-back trees:

    
    
        balance :: Color -> RBTree a -> a -> RBTree a -> RBTree a
        balance B (Fork R (Fork R a x b) y c) z d = Fork R (Fork B a x b) y (Fork B c z d)
        balance B (Fork R a x (Fork R b y c)) z d = Fork R (Fork B a x b) y (Fork B c z d)
        balance B a x (Fork R b y (Fork R c z d)) = Fork R (Fork B a x b) y (Fork B c z d)
        balance B a x (Fork R (Fork R b y c) z d) = Fork R (Fork B a x b) y (Fork B c z d)
        balance k a x b = Fork k a x b
    

The lhs does the dirty work of identifying the relevant bits in the tree and
in the rhs things get rebalanced in a similar form.

(Another thing I find amusing in this example is that its one of the few
examples of Haskell code where you can't get around the "copypasting" by using
`let` or `where`)

------
thomasz
Speaking about F# and readability:

Reading F# (and Ocaml) without an editor sucks:

\- Nobody bothers to include type or visibility annotations in function
prototypes. Signature files are not local and thus are not included in diffs
etc. If you think that doesn't matter, go to
[https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSha...](https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/seq.fs#L261)
and tell me the type of generateWhileSome.

\- The order of compilation matters, and is defined in the Build script:

> Essentially, I either start from the first line of the first file, and read
> forward, or the last line of the last file, working my way back."

That only works in the IDE. On github, the workflow goes like this: Open the
build file, look for the first file. Find that file in the directory
structure. Read that file and go back to the build script, rinse, repeat ad
nausea.

~~~
kvb
There's no reason that this needs to be the case; see Tomas Petricek's tools
for producing tooltips for F# code on the web [1][2]

[1] <http://tomasp.net/blog/fswebsnippets-intro.aspx> [2]
<http://tomasp.net/blog/fsharp-literate-programming.aspx>

~~~
thomasz
Seems like the only thing left to do is getting patches into cat and diff.
Seriously, adding support for inline signatures would solve the problem in a
general way. Why not do this?

    
    
        val map : f:('a -> 'b) -> list:'a list -> 'b list
        let map f list = (*...*)

------
lysium
I'm all with succinctness. I'm wondering how much this has to do with F# per
se than with imperative vs. functional programming language.

I think the same applies to Java vs. Scala / Clojure. (For example, in German,
[http://funktionale-programmierung.de/2013/02/26/scala-
java-a...](http://funktionale-programmierung.de/2013/02/26/scala-java-
ant.html) \-- Google Translate: <http://goo.gl/CF1q9>)

------
Jabbles
Can you really claim that it's a "one-liner" just because you can format it so
that it has no newlines?

You can do the same thing with C, but I wouldn't let anyone do it in my
codebase. Perhaps the F# community feels differently?

------
rcoh
503....Whoops.

