Showing posts with label names. Show all posts
Showing posts with label names. Show all posts

Don't abbreviate rare names

Some languages are too consistent about keeping their names short. Arc and Wart call their macro-defining operators mac instead of defmacro or define-macro.

I understand how a designer could see mac as an important operator. If you think of macros as a central, distinctive feature of your language, and if you use quite a few of them to bootstrap your standard library, they feel important enough to deserve a short name.

mac does almost nothing for the length of programs, though. Macro definitions, however fundamental, aren't common enough for it to matter. define-macro is short enough. I prefer defmacro, but only because it follows a naming convention that makes other, more common names shorter; it's not common enough itself to justify an irregularly short name.

Save the aggressive abbreviation for common operations like make-hash-table. Giving that a one-word name (or even {}) makes more difference.

Other uses for Unicode

Unicode identifiers are impractical because users can't type them. But languages can safely use Unicode characters for many other purposes. For example:

  • Prompts. Doesn't every language need a distinctive prompt? Why not adopt a Unicode character, so the prompt won't look like anything users type?
  • Alternate names: If you allow one symbol to have multiple names, the pretty-printer can use Unicode while humans type in ASCII. Doing a lot of this would interfere with learning, but basics like printing Haskell \ x -> x as λ x → x shouldn't hurt much.
  • Alternate reader syntax: Similarly, print can use Unicode characters in place of the least satisfactory ASCII ones. Wouldn't it be nice if vectors printed with proper angle brackets instead of #(...)?
  • Unreadable objects: If an object's printed representation can't be read back in, it need not be typed by humans, so it can use arbitrary characters. This might improve the printed representations of functions, classes, promises, etc., which often print unhelpfully at the REPL.
  • Non-ASCII art: Languages (and libraries) often print everything as text, even things like tables that shouldn't be, because that's the only output interface they can rely on. Unicode can help make this less ugly. (Box-drawing characters!)
  • Documentation and error messages: you can use whatever you want here.

REPLs, by the way, should exploit terminal control before worrying about Unicode. Small improvements like coloring and a syntax-aware line editor make a big difference to usability. Not everyone has an IDE or wants to use it for everything.

Maps vs. map

In functional languages, map traditionally refers to everyone's favourite1 high-order function. C++ and Java, on the other hand, follow a common mathematical usage: a “map” is a dictionary (an associative array, if anyone still uses that term). Clojure and Scala, being of mixed parentage, have both: their map is the high-order function, and their dictionaries also have names containing Map. This is only a little bit confusing, but both concepts are so important that they ought to have unambiguous names.

I usually prefer to reserve map for the function, and dismiss the use of the same name for dictionaries because it's less familiar and it doesn't make much sense. But map doesn't make much sense as a name for the function either. You can make excuses for it (it maps old elements of the list to new ones? it maps2 the function across the list?) but they're not very compelling; you can make equally good excuses for many other functions. The name is standard, but it's not a very good one.

Meanwhile, dictionaries are a common concept in need of a good name. “Dictionary” is four syllables, and “associative array” six (and misleading). “Hash” is too specific (and also a little misleading, since it ought to mean a hash function). But “map” is short, already somewhat well-known, and if not clear then at least not actively misleading. So maybe I should give up and call them maps, and find some other name for map-the-function.

Is this outrageous?

There's no obvious better name. image might be clear to mathematicians, but it's opaque to everyone else. elementwise is too long. each is comfortable, but suggests for-each (as in Ruby) or every. Repurposed prepositions like across or through are opaque, and tend to collide with natural-language uses (consider how annoying the C++/Java this is in speech). Symbolic names are even more opaque, not to mention unpronounceable. Perhaps it acquired the name map for lack of a good alternative.

It might be possible to avoid the issue by making the function unnecessary. If scalar operations are overloaded to operate elementwise on collections, as in APL, explicit map would be much less common, so its name would be less important. It wouldn't be rare enough to ignore, though; many of its calls use operations that aren't scalar-only, or at least aren't obviously so, so they would still be written with explicit map.

Another alternative is to merge it with some other function. If collections are seen as functions, map is almost equivalent to compose (especially in a lazy language). If compose on a collection does map, then there's no need for a separate map function, especially if compose has a short name like . or . However, I have some difficulty understanding f . xs as map f xs. Composition and elementwise application may nearly coincide in meaning, but they're conceptually quite different.

1Except possibly for compose.

2This use of “map” as a verb is presumably derived from the function, so it's a poor excuse.

How typep got its name

Paul McJones has a large collection of old Lisp manuals, which shed a lot of light on historical questions like how unary typep got its irregular name. The earliest reference I've found is in a 1973 Maclisp manual, which describes it thus, immediately after the other type predicates:

typep  SUBR 1 arg
  typep is a general type-predicate.  It returns an atomic symbol
  describing the type of its argument, chosen from the list
    (fixnum flonum bignum list symbol string random)
  symbol means atomic symbol.  Random is for all types that don't
  fit in any other category.

A “general type-predicate”? This wording suggests typep got its name because it was seen as a generalization of the regular type predicates. This makes sense: as Maclisp acquired an increasingly unwieldy number of type predicates, its maintainers would want to merge them into one operation, and would probably think of that operation as “a general type-predicate”, even though it's not actually a predicate.

(That random type is probably just an implementation detail showing through, but it's still adorable. Rudimentary type systems are to languages what tiny clumsy paws are to kittens.)

The curious attraction of one-argument typep

In Maclisp, the function typep takes one argument and returns its type in the form of a symbol. This is a fundamental operation, and it's egregiously misnamed, as it's not a predicate. (The -p convention is much older than typep, so the name was misleading even in Maclisp.) Lisp Machine Lisp preserved it for compatibility, but Common Lisp fixes the confusion by calling it type-of and using typep only for the two-argument type membership predicate.

I've never used Maclisp, nor any dialect with one-argument typep, so this misnaming should interest me only as historical trivia. But I find it oddly compelling. When I read typep, I think of the unary version first, and only remember the type membership test when context reminds me. When I refer to the “what type is this?” operation without having a specific language in mind, I tend to carelessly call it typep.

Why? I suspect it's because the most obvious name, type, is too bare — it sounds like it might collide with something else, although it doesn't in either Maclisp or Common Lisp — so I look for an affix to distinguish it. The -of in type-of works, but it's meaningless and irregular, and sounds wrong — more like function application than part of a name. -p is semantically inappropriate, but it's so common and familiar that it sounds right anyway. So I latch onto it and don't think about the meaning.

I can't be the only one with this problem. I've heard other people call it “one-argument typep”, and someone must have made the same mistake decades ago to give typep its misleading name in the first place. (Was it derived from a predicate? Did its designers have a different interpretation in mind, like “return the type predicate that's true of this value”?) If you are also drawn to this misnomer, or if you know more of its history, I'd like to hear about it.

Followup: How typep got its name

Short names are addictive

This morning I tried to do this in Common Lisp:

(defun neighbors (x y)
  (mapcar (fn (dx dy) (vector (+ x dx) (+ y dy)))
          '(-1 0 1 0)
          '(0 1 0 -1)))

Naturally, SBCL spit out half a page of warnings, starting with this:

;     (+ X DX)
; 
; caught WARNING:
;   undefined variable: DX

;     (DX DY)
; 
; caught STYLE-WARNING:
;   undefined function: DX
; 
; caught WARNING:
;   undefined variable: DY

It took me an embarassingly long time to figure out why. I have become so used to fn being lambda — in Arc, Clojure, and any number of other new Lisps, including my own — that I didn't notice anything out of the ordinary.

I did at least remember that CL can't add vectors, which was how I wanted to write it. This is a hazard of learning new languages, and of designing them: improvements, even such minor ones as shorter names, are addictive. They're fun at first, but soon you become dependent on them, and the old ways seem unbearably clunky, if you can even remember them.

Is there a better name for generic cons?

Any serious programming language has a variety of collections in its library. Most reduce the resulting complexity by overloading the common operations for multiple types. But few include cons among the generic operations. This is partly because it's unnatural for many types - only the functional collections like lists and trees support it efficiently. But it may also be a problem that the operation has no obvious name.

Dylan calls it add, which sounds like arithmetic. Clojure calls it conj, for conjoin, which is unfortunately confusable with the complex conjugate function. C++ has push, but that's implicitly imperative. Other possibilities: extend, augment (or aug?), with, or maybe a more abstract symbol like &. Does anyone have a better idea?

how-much-space-do-those-hyphens-take?

In a rant about naming conventions, Yossi Kreinin praises the way Lisp does multi-word names:

I think that the best naming convention out there is the Lisp one: case-insensitive-dash-separated. It just doesn’t get any better:

  • You never have to hit Shift or Caps Lock in the middle of a name, which makes typing easier. This is especially important for names because you use auto-completion with names. Auto-completion requires you to press things like Ctrl-Space or Alt-Slash or Ctrl-P. Together with another Shift needed for the name to be completed correctly, auto-completion is much more likely to cause repetitive strain injury.
  • You never have to think about the case. Figuring out the case of a letter in a case-sensitive naming convention can be non-trivial; more on that later.
  • The dash-as-a-separator-convention is used in English, so your names look natural.

I'm not so fond of this convention. Well, it is nice to read; I can't think of anything more natural. And case-irrelevance is nice. (If your favourite lisp stopped case-folding one day and became case-sensitive in practice as well as theory, would you even notice?) There's just one problem: those hyphens take space.

How much?

(let ((syms 0) (chars 0) (hyphens 0))
  (do-symbols (s (find-package :cl))
    (incf syms)
    (incf chars (length (symbol-name s)))
    (incf hyphens (count #\- (symbol-name s))))
  (format nil "~S symbols, ~S chars (avg. ~2,2f)~%~
             ~S hyphens, ~2,2f% hyphens (avg. ~2,2f)~%"
    syms chars (/ chars syms 1.0) hyphens
    (/ hyphens chars 0.01) (/ hyphens syms 1.0)))

"978 symbols, 11271 chars (avg. 11.52)
875 hyphens, 7.76% hyphens (avg. .89)"

Hmm. Not as bad as I thought. 7.7% sounds like a lot, but most of those hyphens are in rarely-used names; the common ones are short. The big expression above has only four hyphens (plus the #\-), so it's not a huge problem. On the other hand, standard library names (even in CL) tend to be short; user code is often full of rather long names. Let's see...

(defun slurp (filename)
  "Read the contents of a file as a string."
  (with-open-file (s filename)
    (let* ((len (file-length s))
           (str (make-string len)))
      (do ((i 0 (1+ i)))
          ((>= i len) str)
        (setf (schar str i) (read-char s t)))))

(defun count-hyphens (filename)
  (let ((s (slurp filename)))
    (format t "~2,2f% of ~S chars~%"
    (/ (count #\- s) (length s) 0.01) (length s))))

(count-hyphens "asdf-install/installer.lisp")
3.13% of 12210 chars
(count-hyphens "edit-modes/python.el")
3.13% of 90348 chars   ;coincidence
(count-hyphens "keywiz.el")
2.88% of 10230 chars
(count-hyphens "tetris.el")
4.54% of 22458 chars
(count-hyphens "edit-modes/slime/swank.lisp")
2.86% of 191438 chars

There's a lot of variation (partly because some files are diluted with lots of comments, which I didn't try to strip out), but evidently hyphens are still significant. Tetris.el has a lot because it has a lot of names with short words, like tetris-top-left-x. Elisp in general suffers (in total length as well as hyphens) because it has no module system, so every name needs a module prefix. (If you want to make a language's code shorter, this is the first thing to worry about: make sure users don't have to defend against name collisions.) But even in CL, hyphens take around 3% of the code. That's about half as much as parentheses.

Maybe this isn't directly a problem. People probably don't look at the hyphens even as much as they look at parentheses, so what does it matter how many there are? But they still lengthen lines, making the eyes move farther, and increasing the chance of linebreaks. When one character accounts for 3% of your code, it's hard to argue that it doesn't matter. But as with parentheses, it's even harder to do anything about it, since all the alternatives are harder to read. So we use hyphens. But I still don't feel altogether good about them.

Update: It seems some people think I'm bashing Lisp on the grounds that hyphenated names make its code 3% longer. Relax; of course this is not a big problem, and I like Lisp, or I wouldn't be worrying about its naming conventions. And I prefer hyphens to every alternative, because they're so much more readable. But this readability isn't free, and at 3% the cost (in screen space) is surprisingly high. I don't have a solution, but it's nice to be aware of the problem. I will take CamelCase a little more seriously now.

== is not so bad

Here's a language feature I (and many others) love to hate: using == as an equality test, while = means define or even assignment. It's easy to object to this on the grounds that it abuses =, but this abuse is so common that it's not really confusing any more.

Update 13 Feb: Conveniently, equality is the only common meaning of ==. Using it for anything else would be confusing. = has so many meanings that it hardly suggests anything, misleading or not.

There's also a minor advantage to this choice of names. define (or assignment, in imperative languages) can be considerably more common than equality tests, so we can save characters by giving it the shorter name. This is a tiny savings, but it is in the most important part of a language. Top-level definitions, and especially their first lines, are read more often than other code, because you scan them while searching for the definition you want. So if it makes Haskell-style definitions a little easier to read, using = could be worth a little confusion.

The three-line = sign (, if you have that character) would be better, but it's not a practical option yet. So I suppose I should stop complaining about = and enjoy its readability.

Lessons from Goo

Anyone following recent Lisp dialects is probably aware of Jonathan Bachrach's Goo, a descendant of Dylan that has returned to its Lispy roots. It's not being developed any more, but it's worth studying for a language designer, because it is the best-designed recent Lisp. I've learned a number of things from it that I did not expect.

Easy variables

Goo's internal def is more convenient than let. Because it doesn't affect the indentation of the code that follows it, it makes the common operation of adding or removing a lexical variable much easier. It's annoying that def is distinct from dv, and that the other defining forms have no internal version (especially df). And of course it only works in a seq, so it's not always an option. A def that worked in more contexts would be a big step torward eliminating the common hassle of creating a variable when a value is used twice.

Short names

Short names are a win, of course, but you can go too far. Many of Goo's names - df, <lst> - omit a vowel to save one character, at the cost of making the name unpronounceable. So one-syllable "def" and "list" become "D.F." and "L.S.T." in speech. Names are read aloud often enough that this matters more than the decrease in characters. The authors say "goo opts for pronounceable special forms as much as possible", but some of the most frequently used names aren't.

Short names also have more collisions. Goo used to abbreviate define-slot as ds, but there weren't enough two-letter names to go around. It was later renamed to dp (for define-property) so ds could become define-syntax. I don't think that's common enough to deserve a two-character name, but Goo's names are either full-length or tiny. There's no convention of intermediate length, so defsyntax (or, for that matter, the traditional defmacro, which ds closely resembles) wasn't an option. Flexibility in naming conventions is important.

Speaking of naming conventions, the <lst> convention for classes is annoyingly long. It looks short because you don't pronounce the brackets, but those two extra characters are repeated quite often in type declarations. The problem that motivates this convention is that class names want to be overloaded as constructors. In a language where anything can be callable, this would be easy to avoid: a class is its own constructor, so there's no need for another name.

The shorter the names get, the more important conciseness of other syntax becomes. Parentheses and spaces start to dominate the character count. Getting rid of those takes syntax, which is hazardous stuff. Goo uses a little of it to shorten lambda and arglists.

Research vs. development

Goo suffers from being a compilation research project. The implementors have spent much of their effort on on-the-fly translation to C. The result is reasonably fast, and the ability to write inline C is interesting, but other aspects of the language are rather unfinished. There are other experimental features, like dp, that are awkward in practice. I don't blame the authors for not trying very hard to make the language useful, but if they were, this would not be a good strategy.

op wins

Goo's op macro makes partial application easy, and it's more flexible than curried functions. Every functional language should have it.

SRFI 26 has two kinds of op (called cut and cute), differing in whether the argument subforms are evaluated once or on each call. (Despite the cute name, I had to look up the SRFI number. Unmemorable numbers are SRFIs' biggest problem.) This distinction rarely matters, because the arguments are almost always either variable references or literals. In all the code I've written using op, I have never had one that was expensive or had side effects. As it happens, my op implementation, like Goo's, reevaluates the arguments each time - but I didn't know that until I checked.