11

Consider the following problem: given a list of length three of tuples (String,Int), is there a pair of elements having the same "Int" part? (For example, [("bob",5),("gertrude",3),("al",5)] contains such a pair, but [("bob",5),("gertrude",3),("al",1)] does not.)

This is how I would implement such a function:

import Data.List (sortBy)
import Data.Function (on)

hasPair::[(String,Int)]->Bool
hasPair = napkin . sortBy (compare `on` snd)
  where napkin [(_, a),(_, b),(_, c)] | a == b = True
                                      | b == c = True
                                      | otherwise = False

I've used pattern matching to bind names to the "Int" part of the tuples, but I want to sort first (in order to group like members), so I've put the pattern-matching function inside a where clause. But this brings me to my question: what's a good strategy for picking names for functions that live inside where clauses? I want to be able to think of such names quickly. For this example, "hasPair" seems like a good choice, but it's already taken! I find that pattern comes up a lot - the natural-seeming name for a helper function is already taken by the outer function that calls it. So I have, at times, called such helper functions things like "op", "foo", and even "helper" - here I have chosen "napkin" to emphasize its use-it-once, throw-it-away nature.

So, dear Stackoverflow readers, what would you have called "napkin"? And more importantly, how do you approach this issue in general?

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
gcbenison
  • 11,723
  • 4
  • 44
  • 82
  • 7
    `go` is the way :) – Riccardo T. May 24 '12 at 15:25
  • 2
    A general metric - _use short descriptive names, if you struggle to be suitably descriptive just make them short_. By the way `napkin` as currently written is not very robust (the list must have exactly three members), if you think about it a bit more maybe it will suggest a better name. – stephen tetley May 24 '12 at 16:44
  • 3
    @stephen Yeah, I realized that about `napkin` as I was writing it. Would you say this is true in general though: if you're having a hard time naming a function, your design probably needs improvement? If true, that's a pretty powerful concept... – gcbenison May 24 '12 at 17:31
  • @gcbenison - for top-level functions, I agree with your maxim. Convoluted names are a signal that something isn't quite _worked out_ enough. – stephen tetley May 24 '12 at 20:34
  • Wouldn't a new hasPair defined in a where close take precedence and hide the outer one? In such a short piece of code, there is little risk of confusion ... – Vincent Beffara May 26 '12 at 21:04
  • @vincent Yes you're right, I hadn't realized that `hasPair` would work for the name of the helper function. Lots of people generally consider hiding outer bindings to be harmful, though. – gcbenison May 26 '12 at 22:16

4 Answers4

25

General rules for locally-scoped variable naming.

  • f , k, g, h for super simple local, semi-anonymous things
  • go for (tail) recursive helpers (precedent)
  • n , m, i, j for length and size and other numeric values
  • v for results of map lookups and other dictionary types
  • s and t for strings.
  • a:as and x:xs and y:ys for lists.
  • (a,b,c,_) for tuple fields.

These generally only apply for arguments to HOFs. For your case, I'd go with something like k or eq3.

Use apostrophes sparingly, for derived values.

Community
  • 1
  • 1
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
3

I tend to call boolean valued functions p for predicate. pred, unfortunately, is already taken.

sclv
  • 38,665
  • 7
  • 99
  • 204
2

In cases like this, where the inner function is basically the same as the outer function, but with different preconditions (requiring that the list is sorted), I sometimes use the same name with a prime, e.g. hasPairs'.

However, in this case, I would rather try to break down the problem into parts that are useful by themselves at the top level. That usually also makes naming them easier.

hasPair :: [(String, Int)] -> Bool
hasPair = hasDuplicate . map snd

hasDuplicate :: Ord a => [a] -> Bool
hasDuplicate = not . isStrictlySorted . sort

isStrictlySorted :: Ord a => [a] -> Bool
isStrictlySorted xs = and $ zipWith (<) xs (tail xs)
hammar
  • 138,522
  • 17
  • 304
  • 385
  • Yes, this seems to echo @stephen's point that better design naturally leads to better names. – gcbenison May 25 '12 at 13:27
  • Would that use of `hasPair'` be consistent with @don's "Use apostrophes sparingly, for derived values"? – gcbenison May 25 '12 at 13:28
  • 1
    I second this. Don't be afraid of a few extra top-level definitions, this especially makes debugging easier as you can inspect the behavior of the supporting functions in isolation of the supported. Use explicit module exports if you are concerned about exposing a particular API. – Dan Burton May 26 '12 at 20:02
1

My strategy follows Don's suggestions fairly closely:

  1. If there is an obvious name for it, use that.
  2. Use go if it is the "worker" or otherwise very similar in purpose to the original function.
  3. Follow personal conventions based on context, e.g. step and start for args to a fold.
  4. If all else fails, just go with a generic name, like f

There are two techniques that I personally avoid. One is using the apostrophe version of the original function, e.g. hasPair' in the where clause of hasPair. It's too easy to accidentally write one when you meant the other; I prefer to use go in such cases. But this isn't a huge deal as long as the functions have different types. The other is using names that might connote something, but not anything that has to do with what the function actually does. napkin would fall into this category. When you revisit this code, this naming choice will probably baffle you, as you will have forgotten the original reason that you named it napkin. (Because napkins have 4 corners? Because they are easily folded? Because they clean up messes? They're found at restaurants?) Other offenders are things like bob and myCoolFunc.

If you have given a function a name that is more descriptive than go or h, then you should be able to look at either the context in which it is used, or the body of the function, and in both situations get a pretty good idea of why that name was chosen. This is where my point #3 comes in: personal conventions. Much of Don's advice applies. If you are using Haskell in a collaborative situation, then coordinate with your team and decide on certain conventions for common situations.

Dan Burton
  • 53,238
  • 27
  • 117
  • 198