6 ms·
I disagree with his example of 'unintelligible' code. He suggests replacing match list with | x::xs -> ... with match list with | head::t
by crntaylor 14y ago
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!
- sdevlin 14y ago> 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.
- thomasz 14y agoIt's a pain in the ass, and the only benefit I can think of is that it made the compiler easier to implement.
- snprbob86 14y agoMany 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.
- thomasz 14y agoI'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?
- snprbob86 14y agoIf 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.
- watt 14y agoOh 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 14y agoSurely 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 14y agoYou will end up with just another fringe write-only language.
- chc 14y agoI 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.
- abraininavat 14y agoIs 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.
- 14y ago
- kvb 14y agoI 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.
- mich41 14y ago> 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.
- sitharus 14y agoYes, 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.
- lysium 14y ago> 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...
- gtani 14y agoIf 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-declarations http://stackoverflow.com/questions/1378575/f-forward-type-de... __________ Also Xamarin shd be releasing... soon http://news.ycombinator.com/item?id=5251413 http://news.ycombinator.com/item?id=5251413
- Associat0r 14y ago> 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 http://cs.hubfs.net/topic/None/59219#comment-70220
- mokus 14y agoI'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.
- jamesaguilar 14y agoThat'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.
- flaie 14y agoYou'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.
- mich41 14y agoMaybe 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.
- flaie 14y agoI'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.
- waxjar 14y agoI 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.
- pash 14y ago> ... 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.
- pash 14y agoWhoops, 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