Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
On F# code readability (clear-lines.com)
38 points by gebe on March 4, 2013 | hide | past | favorite | 70 comments


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!


I've long felt that very-short names like "x" and "i" should be treated like pronouns in English. They should only be used when the scope is fairly limited and there shouldn't be too many in use at once. There are probably other sensible guidelines I've missed here.

When used well they often make code more readable than "descriptive" names, just like most English text is more readable with pronouns than without. In any case, categorically claiming they are bad makes about as much sense to me as banning pronouns from English.


That's a really great rule of thumb. I think I might use that. x::xs might not be so nice if the body of the match has twenty lines in it, especially if y::ys also exists. That'd be the pseudo-equivalent of saying, "Joe and Sam were getting food and he wanted a burger but he wanted steak so he finally argued that they should go to Bob's Grill which had both and he agreed." (Not really, because the sentence is actually totally ambiguous whereas with variables it just involves an extra lookup for the reader.) But if the body is one or two lines, there's not much purpose in the extra characters.


> 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.

Here is great explanation on the benefits of this. http://cs.hubfs.net/topic/None/59219#comment-70220


> 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.

IIRC in OCaml you have to compile modules in topological order wrt dependencies.


> ... 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!

Yes, but the names you assign in a pattern get used outside the pattern. Presumably there will be an x/head or an xs/tail on the right side of the arrow in your example, and it's there that meaningful names help.

That said, I'm quite fond of the x:xs naming convention. It's particularly useful when you're doing something with two or more lists in the same scope, and you pattern-match to produce multiple heads and tails. For instance (Haskell syntax, as I don't know F#):

  zip :: [a] -> [b] -> [(a, b)]
  zip (x:xs) (y:ys) = x : y : zip xs ys
  zip _ _ = []
This also illustrates when you might well disregard the trope of using "meaningful" names. The code above is polymorphic and will work for any types a and b, and for any values of those types, no matter what they represent. Functional programming really encourages this sort of generality. Yes, it's very much the case that, as the article's author wrote, "functional code tends to focus on applying generic transformations to 'things', and not that much on what the 'thing' might be."

But, if it helps to clarify what you're doing at the domain level, you can (and should) assign meaning through types and by renaming and adding restrictive type signatures to polymorphic code:

  data Child = ...
  data Chore = ...
  type Assignment = (Child, Chore)

  assign :: [Child] -> [Chore] -> [Assignment]
  assign = zip
This not only helps readers understand what's going on, it also enlists the type-checker in helping to ensure you've, say, assigned chores only to your kids and not to your pets.


Whoops, I changed my example midway through and ended up with something screwy. The first case of the definition of zip above should of course read:

  zip (x:xs) (y:ys) = (x, y) : zip xs ys


I agree that using head::tail is usually not more readable, though I think that sometimes it is better to be a bit more descriptive than just x::xs (e.g. key::keys). It really depends on the context as well as the experience level of the people who will be reading the code.

It is true that the order of files matters. Presumably this is for the same reason that order matters within a file.


> 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?

He is correct. (At least, this was true a couple years ago. I haven't used F# since then, but I doubt this has changed.)

I don't have enough F# experience to speak to any potential benefits of this restriction.


It's a pain in the ass, and the only benefit I can think of is that it made the compiler easier to implement.


Many languages require declare-before-use and co-declared recursive structures. Sometimes, they provide an escape-hatch like a forward-declaration. It also makes the compiler substantially faster and makes it far harder to accidentally introduce a layering violation in your design.

If you're a Java or C# programmer, or even a C++ programmer, you're used to cyclic relationships all over the place. Your Person class has a .Adresses property and your Address class has a .Residents property so that you can conveniently person.Addresses.Filter(address => address.Residents.Count > 1) and suddenly your Address class is tightly coupled to your Person class. Do this a few dozen times and before you know it, your software is a maintenance and refactoring nightmare.

Object-oriented programming encourages this design. Functional programming discourages it. Modern OOP compilers conveniently resolve circular dependencies (Java does, C++ doesn't without header games). Modern FP compilers force you to declare and contain your circular dependencies.

Lastly, declare-before-use makes a lot of dynamic and live programming scenarios viable. Lisps, for example, generally have a compilation unit of a top-level form. This way the REPL and a normal source file behave more similarly, if not precisely the same.


I'm totally fine mandating explicit declarations for mutual dependencies, I'm just pissed that F# has no syntax for dependent modules.

It's just odd. Why can I declare co-dependent functions and types, but not modules?


If they are co-dependent, then that, by definition, means that they are not independent. What value do you get by having co-dependent modules that you can't get by having a single module? If you want two public APIs with one common implementation that is co-dependent, you can have three modules where two depend on the co-dependent part. Without seeing a motivating use-case, I'm just going to assume that F# is succeeding in preventing you from making a bad design decision.


If you were writing for haskell/scala/ML'ers, x::xs, for C#ers, head::tail

I haven't been tracking closely but no Forward declarations was one of common complaints, along with organizing projects in subdirectoris (I think they have fixes for that now) and VS support in general

http://stackoverflow.com/questions/1378575/f-forward-type-de...

__________

Also Xamarin shd be releasing... soon

http://news.ycombinator.com/item?id=5251413


You're right about the fact that on the left on `::` can never be anything other than the head of a list.

But I'd like to add that throughout almost all OCaml code I've written and used to read, that type of matching was written:

    match list with 
        | h::t -> 
Wich reads easily `h` for `head` and `t` for `tail`. I'm surprised it hasn't been reproduced in the F# world.

`h::t` seems even better to me than `head`/`tail` or `x`/`xs` which for the second one, means nothing really to me.


Maybe your code isn't famous enough. I've seen lots of x::xs's everywhere, but never a single h::t.

Personally, I always use x::xs in one-liners and something meaningful (no, neither head nor h is meaningful) in longer matches.


I've never mentioned my code as being famous. I've always used h and t in one-liners, maybe because of the person who taught me OCaml.

H and t can also be found in the official Caml and batteries source code, along with hd/tl, a/l and probably x/xs if it pleases you.

I can't agree more about naming variables correctly where it makes sense though.


Yes, order of files in a project matters. If Things.fs contains type Foo and Main.fs uses it, then the project must look like

    MyProj
    |- Things.fs
    |- Main.fs
Visual Studio has a few functions to manage this order.

The benefit is faster compilation, because F# requires recursive types to be declared together, such as

    type Foo = A of Bar | B
    and Bar = {Baz: Foo}
this doesn't really come up that much.


> this doesn't really come up that much.

Unless, of course, if you don't know or expect that 'rule' and you are wondering, why your type is not visible in that other module for over an hour...


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.


> 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).

I use OCaml and I think of it as idiomatic naming for pattern matching on a list, in a similar way to how iteration variables are often named `i`, although `index` might be more superficially clear, but in reality it is not really since one "knows" `i` is the interation variable, just like `x::xs` means `x` is an element and `xs` is a list of x'es, that is elementy of the same type as x. Pretty clear in my opinion.


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.


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


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.


I do like the principle of thinking about code readability. But unless you're writing an introductory tutorial, your code should absolutely not be optimized for reading by people who know nothing about the language. You should try to make it readable for the people who will actually be working with the code.

Optimizing code for people who don't know anything about the language basically means sticking as close to the C or Python way of doing things as possible. If your language is significantly different from C, this is obviously not going to be a global maximum of readability.


Is readability to people who don't know the first thing about F# at all relevant? If that's the most important metric then we should write out all our programs in plain English.

Readability to F# programmers is what matters, along with a host of other concerns, such as terseness.


Actually, I think there's some merit to writing code that is readable to outsiders, for several reasons. First of all, with a not-quite-mainstream language like F#, you're more likely to have team members joining your projects who are lacking the relevant in-language expertise. Making code more readable for them without significantly harming readability for an expert seems like a fine tradeoff. Secondly, one benefit to F# is that the code can read almost like pseudocode, which opens up the possibility of having domain experts (that is, non-programmers) review code, in which case the same tradeoff also makes sense (see e.g. Jane St. Capital, where traders review some/most of their OCaml code).


>so "x::xs" looks alien and very unapproachable

And how is head::tail any better? That's like insisting for loops use "counter" instead of "i".


It's better - in so many ways that I will necessarily offer you just a short summary of some of them.

Being able to figure out a meaning of something is crucial in programming. We spend much more time reading the code than writing it and most of the time during reading is spent on resolving the meaning of symbols. To be able to tell what a symbol means, one needs to take into account many things - from OS it's running on to language it's written in to declared variables in the scope. And much more, of course.

Now back to your example: to be able to understand `x::xs` I need to know that `::` is a cons operator. Or more precisely, to understand the meaning of `x` symbol I need to take into account another symbol.

On the other hand of the spectrum is something like this: `first_element_of_list :: rest_of_list`. To understand the meaning of the first symbol in this expression you need to know exactly one symbol - itself. That's one benefit. The second is that someone who doesn't know what consing is, let alone how it's written in OCaml, can still understand this expression. So that's the second benefit.

It's worth remembering that we do not write the code for ourselves nor for the computers. We write the code to be read by other humans (always - don't argue). As with any kind of writing, then, it's helpful to know the audience. If I was writing a tutorial on constructing lists in OCaml (for beginners), I would use the second example above. But if I was writing production code (so for me and my team to read) I would use something shorter, like `head :: tail` for example. And if I was writing a quick and dirty script for my personal use, or if I was contributing to the work of hardcore OCaml hackers I would probably write `x :: xs` - because in case of script I'd be convinced that readability doesn't matter and in second case I would be certain that everyone reading and working with the code I wrote would be already so used to the construction that they would read it without problems.

That's another thing to consider: after using a language for a long enough time we tend to internalize its idioms; when this happens we are able to treat bigger constructs as one symbol. This ability helps in reading the code quickly, but it's worth remembering that not everyone developed it. Also, because longer names do not hinder quick-reading of idioms for those who know them, that's not a counterargument to longer names.

As for loops... Well, I do - sometimes - use `i` as a variable name, but I tend not to. Most often, after a while I come back to the code and replace `i` with `idx`. But to even use `i` as a name in the first place it needs to refer to the nonnegative integer value. In Python you generally iterate over collection directly, so you write `for what_is_it in a_collection:` instead of for example: `for i in 1 to 10: an_element = a_collection[i]`. Anyway, I use single letter variables very sparingly and in the smallest scope possible.

What I want to say, I guess, is that descriptive names are important in good programming and that there is really no argument for using non-descriptive ones.


I've been learning Haskell lately but I've seen the same idiom in other functional languages. I was struck by two things you said: you have to know cons'ing to understand x::xs and each symbol, x and xs should be unambiguously named in isolation (words to that effect).

What I see in "x::xs" is one simple expression at a glance. It is the cons that is the central concept in being able to understand the expression. So my next thought was to wonder what on earth someone is doing programming F# if they don't understand the first thing about working with lists? Cons is what makes the abstraction even possible. If you don't get that, longer symbols surely can't help except maybe to hide the magic gobbledygook.


"What I see in "x::xs" is one simple expression at a glance."

That's exactly what I wrote about in this: "after using a language for a long enough time we tend to internalize its idioms; when this happens we are able to treat bigger constructs as one symbol."

The thing here is that I don't believe that you would treat 'head :: tail' any different than 'x :: xs'. I believe that you would still see 'head :: tail' as one simple expression: I certainly would. If so then there is really no reason to avoid using 'head' and 'tail' as names.

"longer symbols surely can't help"

Do you know lisp? If yes, skip this; if not, read on.

I'll give you two example snippets of code in Racket. Both use exactly the same syntax and mean the same; read the first one and try to answer the question what the code does. The read the second one and try it again.

    (define (drt t)
      (define-values 
        (p l) (values 0 (s:l t)))
      (λ (dc x y a)
        (drc dc x y (s:s (s:r t p)) a)
        (set! p (add1 p))
        (when (>= p l)
          (set! p 0))))
Assume that `s:l`, `s:s` and `s:r` are provided from another module that you have only in a compiled form at the moment and that drc is defined in the same module but written in the same style. Can you tell what is happening here?

The second snippet looks like this:

    (define (draw-text text-string)
      (define-values (current-pos text-len) 
        (values 0 (string-length text-string)))
      
      (lambda (drawing-context coord-x coord-y angle)
        (define current-letter (string (string-get-char-at text-string current-pos)))
        (draw-char drawing-context coord-x coord-y current-letter angle)
        (set! current-pos (add1 current-pos))
        (when (>= current-pos text-len)
          (set! current-pos 0))))
Are you able to guess what this does now? Are you able to guess even a bit more than from the first snippet?


>Assume that `s:l`, `s:s` and `s:r` are provided from another module that you have only in a compiled form at the moment

Why are you being so deliberately dishonest, and expecting a response like "oh, your strawman has totally convinced me to write shitty code, thank you so much first year CS student!"? The entire point is that x and xs are not defined elsewhere, they are defined by you, and used on a single line.


"Why are you being so deliberately dishonest"

I'm not - that's how they are defined in Racket.

"and expecting a response like"

Why do you assume so? I'm really curious what is the difference in the readability to someone who doesn't know the syntax.

"first year CS student"

Ok, so I had enough: you're just a stupid troll. Not only you can't back your claims with arguments but now you're (for the third time, no less) using ad hominem "argument"; it's useless talking to you since you're too stupid to formulate sensible response and you're too bad a programmer to even have a chance of knowing something interesting.

"and used on a single line"

Oh, on a single line? That's fine then...


>I'm not - that's how they are defined in Racket.

You are. The argument is that you should be using single letter variables for small scope, simple variables. Like the classics:

    for (i = 0; i < whatever; i++) {
        myarray[i] = something();
    }
and

    myfunction x:xs = dothing x + myfunction xs
When you decide to deliberately misrepresent that argument as "use single letter variables across modules" that is dishonest.

>Oh, on a single line? That's fine then...

Yes, of course it is.


I'm still waiting for your argument as to why is one-letter name better than three-letters one. What is the difference, what makes it so much harder to understand the name with two additional letters? With a link to the source please.


You write bad code. If you are using anything other than a single letter for a simple for loop iteration, then you are making your code less readable, not more. Adding cognitive load to a trivial process is bad, not good.

You summarize your argument by saying descriptive names are important, but your examples do not support that at all. Descriptive names are important for things that need to be described. If I write a function "reverse_string" with a single string argument, that argument had damn well better be named "s". There is no longer name that can convey any useful additional information. Many longer names will be distracting, and make the code harder to read.


"Many longer names will be distracting, and make the code harder to read."

Do you have any source for this? I really can't imagine this happening, seriously.

"You write bad code."

I see, it's one of those days when you just have to be a dick to someone. Ok, no problem, moving on...

To reiterate without needless things involved: can you prove that a) words between 1 and 6 letters of length are significantly harder to read for higher letter counts and b) having longer names, regardless of a, is not beneficial at all? If you can - please do.


Given that 90% of experienced programmers fall on the side of "i or gtfo", the onus is on you to prove that "counter" is beneficial. Any time I see a long variable name, I naturally assume it is important, and has a large scope. When you lie to me with your code, that makes it harder to read, not easier.


"Given that 90% of experienced programmers"

Is this your proof? Nice...

"is on you to prove that "counter" is beneficial"

And why not on you to prove that it's harmful? Because you don't have arguments?

"Any time I see a long variable name, I naturally assume it is important"

Very good habit, keep at it - and please define 'long' by the way. For me it means 14-20 characters; and short means 3 to 6 characters.

"experienced programmers fall on the side of "i or gtfo","

Well, these are probably the same people who claim that comments in code are bad. Both claims are simply stupid. Anyone experienced enough - and not dyslectic... - will tell you that readability counts, that you should never or almost never use any name shorter than 3 characters and that you should comment your code heavily. Whoever tells you otherwise - to put it simply - writes bad code or wants you to write bad code.

I find it amusing to see so many people mistaking familiarity for readability. Just because something is very familiar to you it doesn't mean that it's readable. It's a shame that people are unable to look at the matter objectively and hence they write bad, unreadable, hard to change and extend code.


>Well, these are probably the same people who claim that comments in code are bad.

No, they are not. Please, copy this thread into a text file and hang on to it. Spend a few years actually reading and write code. Then come back and facepalm at yourself.


I have been reading and writing code for twenty years now in about a dozen languages or so. How long do you think I should wait to realize how mistaken I am now? And while we're at it, how many years have you spent writing and reading code and in how many languages?

But that's a digressions, what's important is that you provided not even a single argument; not even one. You cannot expect to convince anyone without even trying.


>I have been reading and writing code for twenty years now in about a dozen languages or so

I would strongly recommend that you stop admitting that.

>And while we're at it, how many years have you spent writing and reading code and in how many languages?

28 years, and I count 18 languages including logo and sh which you might not count.

>what's important is that you provided not even a single argument; not even one.

Yes I have. Ignoring it doesn't make it cease to exist. The vast majority of experienced programmers have already gone through writing code like you when they were beginners, and then realized it was bad and stopped. You can easily verify this yourself by noticing how the vast majority of experienced programmers tell you that you are wrong.

>You cannot expect to convince anyone without even trying.

I do not expect to convince you. You are attempting to convince others to write bad code. I am stating that you are a bad programmer, and that is why you find such resistance when you try to convince people to write bad code. The fact that you focus entirely on strawmen and demanding that other people prove you wrong rather than simply supporting your claims does not help your case.


You know, saying that someone is a bad programmer without reading their code disqualifies you even before you say anything else. And the "majority of experienced programmers" is a "citation needed" thing. Donald Knuth seems to disagree with them, anyway.

I see no other resistance than from you, by the way.

Oh, and also: aside from "citation needed" your argument that "many people think the same" is an argument from authority, which is rather weak thing to do.


>Donald Knuth seems to disagree with them, anyway.

No, he does not. Read his code, it is full of single letter variables.


Unfortunately you're right on this. I read his various works long ago and had not very accurate memory of them.

As a final note in this discussion I'd like to say that you're probably misunderstanding me. I have never said that single-letter names are completely prohibited - just that they should be avoided when possible.

I actually went and did some analysis on the code I think is good: Underscore.js. This is what I found.

I excluded all language keywords like `var` and `if`. I then calculated how many unique names there are in the code and how many of them are 1 or 2 letters long. The result I got was 4%.

I then checked how many times each of the names was used and how many times 1 or 2 letter long names were used, Surprisingly I got almost the same result - 4%.

So, to conclude - if more than 4% of names you use are shorter than 3 characters than you're writing bad - or at least worse than Underscore.js - code. I'm not guilty of this, I checked my own code too. And how about you?

Also, on a more personal note - you're infuriating as a adversary in the discussion because of at least three things: you only answer to parts of arguments that are convenient to you, you don't openly state assumption you're making and you're mixing completely irrelevant things into the discussion. Please improve :)


I don't know any F# either, but I know that in Ruby the order of files does matter. When you define a class that inherits from a class that lives in another file, you'll have to require that one first.


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.


> 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.


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.


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.


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.


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.


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


"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.


Of course, it should be `aStreamPosition` rather than `index` - still not `i` ;)


x::xs or x:xs in haskell is a very common naming pattern for pattern matching on lists. head and tail are functions that operate over lists so using them as a variable would be considered name shadowing. i is good to use as a loop counter in C for the same reason, its widely recognized and people will know what it means.


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.


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.


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.


That depends. I tend to leave small support classes in the same file, but anything public goes to file per type.

Why? Because I have to work with people who don't have resharper installed, and without resharper it's near impossible to find those types without doing a full project search.


Is a search that hard? Go To Definition? Class View?


If you're talking about a project with thousands of classes in a similar number of files, with auto-generated code from an ORM as part of it. Yes.

Search takes at least a minute. Class view is only useful if you know the namespace. Go To Definition doesn't help if you're looking a definition for an interface and you want the concrete implementation.


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.


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`)


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... 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.


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


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 = (*...*)


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... -- Google Translate: http://goo.gl/CF1q9)


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?


503....Whoops.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: