34

I recently started studying functional programming using Haskell and came upon this article on the official Haskell wiki: How to read Haskell.

The article claims that short variable names such as x, xs, and f are fitting for Haskell code, because of conciseness and abstraction. In essence, it claims that functional programming is such a distinct paradigm that the naming conventions from other paradigms don't apply.

What are your thoughts on this?

Jakob
  • 2,588
  • 5
  • 27
  • 34
  • 1
    @OP Community Wiki questions are those which can be considered subjective, or having no definitive answer. This question probably falls into that category. – Neil Aitken Oct 14 '09 at 08:00
  • In that case, I'm making it community wiki. – Jakob Oct 14 '09 at 10:11
  • 1
    Coding standards often call for long variable names in order to fully document everything, but this can be over-done. Short names reduce the amount of screen real-estate required by a given piece of code, and hence let you fit more code onto a screen. This has productivity and qualtity benefits. It also makes it easier to scan the code to find the bits you need to know about because visual clues like indent structure and operators stand out. Hence a balance is required. I think Haskell has it right. – Paul Johnson Oct 17 '09 at 20:07

10 Answers10

34

In a functional programming paradigm, people usually construct abstractions not only top-down, but also bottom-up. That means you basically enhance the host language. In this kind of situations I see terse naming as appropriate. The Haskell language is already terse and expressive, so you should be kind of used to it.

However, when trying to model a certain domain, I don't believe succinct names are good, even when the function bodies are small. Domain knowledge should reflect in naming.

Just my opinion.

In response to your comment

I'll take two code snippets from Real World Haskell, both from chapter 3.

In the section named "A more controlled approach", the authors present a function that returns the second element of a list. Their final version is this:

tidySecond :: [a] -> Maybe a
tidySecond (_:x:_) = Just x
tidySecond _       = Nothing

The function is generic enough, due to the type parameter a and the fact we're acting on a built in type, so that we don't really care what the second element actually is. I believe x is enough in this case. Just like in a little mathematical equation.

On the other hand, in the section named "Introducing local variables", they're writing an example function that tries to model a small piece of the banking domain:

lend amount balance = let reserve    = 100
                          newBalance = balance - amount
                      in if balance < reserve
                         then Nothing
                         else Just newBalance

Using short variable name here is certainly not recommended. We actually do care what those amounts represent.

Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202
  • I think I understand what you mean, but could you provide an example of the difference between the two situations, just in case? – Jakob Oct 14 '09 at 10:13
  • 2
    @Jakob Many functional languages use some variant of the Hindley-Milner type system, where it is very easy to write generic functions and data structures. In this situation, when programming the data structure, you have no reason to name keys and values in your map, say, "studentId" and "studentFile", so you simply name them "key" and "value", or even "k" and "v" (another common convention). – Pascal Cuoq Oct 14 '09 at 11:54
  • 2
    In the first case using X is similar to using i as a for loop counter in C. It is more or less a throw away variable a longer name would harm meaning not help it. In the second case they are actual variables with actual meaning, so good names are important. – stonemetal Oct 14 '09 at 22:50
  • The built-in snd function actually only works on tuples of size 2. The function they're reimplementing in RWH is (!! 1). – yatima2975 Oct 19 '09 at 14:53
  • @yatima2975, thanks! I somehow mixed things up while writing the answer. – Ionuț G. Stan Oct 19 '09 at 15:18
16

I think if the semantics of the arguments are clear within the context of the code then you can get away with short variable names. I often use these in C# lambdas for the same reason. However if it is ambiguous, you should be more explicit with naming.

map                     :: (a->b) -> [a] -> [b]
map f  []               =  []
map f (x:xs)            =  f x : map f xs

To someone who hasn't had any exposure to Haskell, that might seem like ugly, unmaintainable code. But most Haskell programmers will understand this right away. So it gets the job done.

var list = new int[] { 1, 2, 3, 4, 5 };
int countEven = list.Count(n => n % 2 == 0)

In that case, short variable name seems appropriate.

list.Aggregate(0, (total, value) => total += value);

But in this case it seems more appropriate to name the variables, because it isn't immediately apparent what the Aggregate is doing.

Basically, I believe not to worry too much about convention unless it's absolutely necessary to keep people from screwing up. If you have any choice in the matter, use what makes sense in the context (language, team, block of code) you are working, and will be understandable by someone else reading it hours, weeks or years later. Anything else is just time-wasting OCD.

Dale
  • 12,884
  • 8
  • 53
  • 83
  • 11
    Very good points. Functional languages tend to have more single-letter-conventions for variables that tell the accustomed reader what to expect immediately (Brian mentions x, i, and f). For some odd reason conventions vary from language to language when they don't need to. You would see "head::tail" or just "h::t" in OCaml instead of "x:xs". Also, the emacs caml-mode can display infered types, so there is less need to put *that* information in the variable name. I expect other functional languages have a similar feature. – Pascal Cuoq Oct 14 '09 at 11:46
  • 3
    Isn't it just `(total, value) => total + value` ;) Quite an irony to have unintended mutation in a functional programming context... – Dario Aug 22 '10 at 17:59
8

I think scoping is the #1 reason for this. In imperative languages, dynamic variables, especially global ones need to be named properly, as they're used in several functions. With lexical scoping, it's clear what the symbol is bound to at compile time.

Immutability also contributes to this to some extent- in traditional languages like C/ C++/ Java, a variable can represent different data at different points in time. Therefore, it needs to be given a name to give the programmer an idea of its functionality.

Personally, I feel that features features like first-class functions make symbol names pretty redundant. In traditional languages, it's easier to relate to a symbol; based on its usage, we can tell if it's data or a function.

artagnon
  • 3,609
  • 3
  • 23
  • 26
7

I'm studying Haskell now, but I don't feel that its naming conventions is so very different. Of course, in Java you're hardly to find a names like xs. But it is easy to find names like x in some mathematical functions, i, j for counters etc. I consider such names to be perfectly appropriate in right context. xs in Haskell is appropriate only generic functions over lists. There's a lot of them in Haskell, so this name is wide-spread. Java doesn't provide easy way to handle such a generic abstractions, that's why names for lists (and lists themselves) are usually much more specific, e.g. lists or users.

Rorick
  • 8,857
  • 3
  • 32
  • 37
3

I just attended a number of talks on Haskell with lots of code samples. As longs as the code dealt with x, i and f the naming didn't bother me. However, as soon as we got into heavy duty list manipulation and the like I found the three letters or so names to be a lot less readable than I prefer.

To be fair a significant part of the naming followed a set of conventions, so I assume that once you get into the lingo it will be a little easier.

Fortunately, nothing prevents us from using meaningful names, but I don't agree that the language itself somehow makes three letter identifiers meaningful to the majority of people.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
  • When you see `foo bar (x:xs) = bar x : foo bar xs`, isn't it immediately clear that you're looking at a map function? I think this is much more clear than similarly "vague" variable names would be in most imperative languages. – Chuck Oct 14 '09 at 08:06
  • 5
    @Chuck: Actually, only `foo` and `bar` are vague. `x:xs` is idiomatic, so Haskell programmers will know exactly what you mean. – Jørgen Fogh Oct 14 '09 at 09:39
3

When in Rome, do as the Romans do

(Or as they say in my town: "Donde fueres, haz lo que vieres")

Greg S
  • 12,333
  • 2
  • 41
  • 48
fortran
  • 74,053
  • 25
  • 135
  • 175
2

Anything that aids readability is a good thing - meaningful names are therefore a good thing in any language.

I use short variable names in many languages but they're reserved for things that aren't important in the overall meaning of the code or where the meaning is clear in the context.

I'd be careful how far I took the advice about Haskell names

mk12
  • 25,873
  • 32
  • 98
  • 137
Chris McCauley
  • 25,824
  • 8
  • 48
  • 65
1

My Haskell practice is only of mediocre level, thus, I dare to try to reply only the second, more general part of Your question:

"In essence, it claims that functional programming is such a distinct paradigm that the naming conventions from other paradigms don't apply."

I suspect, the answer is "yes", but my motivation behind this opinion is restricted only on experience in just one single functional language. Still, it may be interesting, because this is an extremely minimalistic one, thus, theoretically very "pure", and underlying a lot of practical functional languages.

I was curios how easy it is to write practical programs on such an "extremely" minimalistic functional programming language like combinatory logic.

Of course, functional programming languages lack mutable variables, but combinatory logic "goes further one step more" and it lacks even formal parameters. It lacks any syntactic sugar, it lacks any predefined datatypes, even booleans or numbers. Everything must be mimicked by combinators, and traced back to the applications of just two basic combinators.

Despite of such extreme minimalism, there are still practical methods for "programming" combinatory logic in a neat and pleasant way. I have written a quine in it in a modular and reusable way, and it would not be nasty even to bootstrap a self-interpreter on it.

For summary, I felt the following features in using this extremely minimalistic functional programming language:

  • There is a need to invent a lot of auxiliary functions. In Haskell, there is a lot of syntactic sugar (pattern matching, formal parameters). You can write quite complicated functions in few lines. But in combinatory logic, a task that could be expressed in Haskell by a single function, must be replaced with well-chosen auxiliary functions. The burden of replacing Haskell syntactic sugar is taken by cleverly chosen auxiliary functions in combinatory logic. As for replying Your original question: it is worth of inventing meaningful and catchy names for these legions of auxiliary functions, because they can be quite powerful and reusable in many further contexts, sometimes in an unexpected way.

  • Moreover, a programmer of combinatory logic is not only forced to find catchy names of a bunch of cleverly chosen auxiliary functions, but even more, he is forced to (re)invent whole new theories. For example, for mimicking lists, the programmer is forced to mimick them with their fold functions, basically, he has to (re)invent catamorphisms, deep algebraic and category theory concepts.

I conjecture, several differences can be traced back to the fact that functional languages have a powerful "glue".

physis
  • 406
  • 5
  • 7
1

In Haskell, meaning is conveyed less with variable names than with types. Being purely functional has the advantage of being able to ask for the type of any expression, regardless of context.

Apocalisp
  • 34,834
  • 8
  • 106
  • 155
  • Because Haskell has return-type overloading, you cannot ask for the type of an expression regardless of context. The surrounding context can affect the actual type you get. For example, what's the type of the expression `return "foo"`? It depends on what kind of monad you're "in". – Laurence Gonsalves Jul 07 '12 at 06:51
  • No, the actual type of `return "foo"` is `forall m. (Monad m) => m String`. It's not "in" any particular monad, which is a very important part of what `return` _means_. – Apocalisp Jul 08 '12 at 03:14
  • I can pass the result of an expression whose type is `(Num a) => a` (eg: `fromIntegral x`) to something that can only take an `Int`. That sure smells like return type overloading. Is there any way in which this sort of return type is observably different from return type overloading? – Laurence Gonsalves Jul 09 '12 at 07:04
  • It's not "return type overloading". It's parametric polymorphism, where the parameter is bound by a universal quantifier. You absolutely can ask for the type of an expression regardless of context. The type `forall a. (Num a) => a` unifies with `Int` because `Int` is one of those _all_ `a`, and there exists a `Num Int`. – Apocalisp Jul 09 '12 at 14:50
  • I'll ask again: "Is there any way in which this sort of return type is observably different from return type overloading?" That is, can you actually construct code that behaves differently with universally quantified parametric polymorphism in the return type than it would with return type overloading? – Laurence Gonsalves Jul 09 '12 at 15:46
  • There is no method call being dispatched based on the _return_ type. A value of a type `(Num a) => a` accepts a type `a` and a `Num` dictionary for `a`, yielding a value of that type. Haskell passes types and dictionaries automatically. I can't construct Haskell code that behaves differently with "return type overloading" because such overloading doesn't occur in Haskell. – Apocalisp Jul 10 '12 at 12:26
  • I'm asking you to construct code that would behave differently if Haskell had return type overloading. If all of the code you can write would behave the same either way, and the set of valid programs is not changed, then they are the same thing. I don't know why you're talking about "methods", which are dynamic dispatch. I'm talking about overloading which is static dispatch. My understanding is that the dictionaries passed around for typeclasses are an optimization, as it is always possible to statically determine the actual type. – Laurence Gonsalves Jul 10 '12 at 17:20
  • OK, here is what happened. You said that "you cannot ask for the type of an expression regardless of context". That is not true, so I explained how this actually works. You then latched on to this idea of how "return type overloading" is really the same thing as... what exactly? I honestly have no idea what you're trying to say, or how it's relevant or interesting. What I think you're actually doing is trying to save yourself from the embarrassment of having been caught in the act of talking out of your ass. – Apocalisp Jul 10 '12 at 18:02
  • Uh, no. I'm trying to figure things out. As far as I can tell your view that the return type is `(Num a) => a` isn't actually different from saying the return type is overloaded (ie: ad hoc polymorphism) and hence depends on the context. I've repeatedly asked you to explain how they would be different, but every time you've avoided answering that question. Apparently asking you to explain your reasoning is too much to ask. – Laurence Gonsalves Jul 10 '12 at 18:33
  • The first step to figuring this out is probably letting go of the idea of "return type overloading". A type like `forall a. (Num a) => a` is a universally quantified type. A value of this type is really _a function_. This function receives a value of an existentially quantified type (which might be written `exists a. Num a`). That value _is a pair_ containing a type (like `Int`) and a value (like a `Num Int` instance). – Apocalisp Jul 10 '12 at 19:08
  • I understand that, but I also don't see how it's behaviorally any different from what is called "return type overloading" in other languages. For example, can I take a single `forall a. (Num a) => a` value and evaluate it multiple times for different types (eg: `Int` and `Double`)? – Laurence Gonsalves Jul 10 '12 at 19:13
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/13690/discussion-between-apocalisp-and-laurence-gonsalves) – Apocalisp Jul 10 '12 at 20:25
0

I agree with a lot of the points made here about argument naming but a quick 'find on page' shows that no one has mentioned Tacit programming (aka pointfree / pointless). Whether this is easier to read may be debatable so it's up to you & your team, but definitely worth a thorough consideration.

No named arguments = No argument naming conventions.

Tim Matthews
  • 5,031
  • 8
  • 38
  • 45