(When reading this, please keep in mind I've never written an
interesting program in APL or PHP, so I am speaking from ignorance.)

APL goes far out of its way to generalize the meanings of things.  For
example, it included a factorial operator (missing in dialects like A+
and K), but rather than give an error when fed a floating-point
number, it would give the value of the gamma function at the point
offset by 1 from the argument --- so it coincides with factorial for
integers, but is more general.  The iota operator is fairly useless (I
think) applied to non-scalars, but it is defined so that its useful
scalar behavior is just a special case of its more general behavior
when fed vectors of any length, pretending that the scalar is a vector
of length 1.

Along the same lines, APL's representation of booleans as 0 and 1
allows you to use +/ to count the number of times something is true;
in K, the 'and' and 'or' functions have additionally been dropped in
favor of the dyadic floor and ceiling functions, which act as 'and'
and 'or' over the domain of 0 and 1.

And then there's its infamous use of every built-in function character
to denote two different functions, depending on whether it is applied
to two arguments or one.  Some characters have even more meanings; I
think / has three (one as the "reduce" operator), and in K I think it
has four.

These aspects of its design are designed to remove redundancy from
your program, making it shorter and more likely to mean something when
you screw it up, so it gives the wrong answer rather than simply raise
an exception.  In general, I do not like this tendency to err quietly.

I was reminded of this language strategy of nonredundancy while
reading the documentation for ocamllex.  

Ocamllex is a lexer generator similar to Mike Lesk's lex, and in the
regular expression patterns that define tokens, you can bind a
variable to a part of the pattern, and there are some rules that
define what the type of the variable is --- whether it's a char, a
string, a char option, or a string option.  (This being Ocaml, you can
do different things with a string, a char, and a string option, so
it's important to know which one you have if you want your program to
compile.)

This reminded me of the big language difference between Python and
both Smalltalk and OCaml, which is that Python has relatively few
kinds of collections, while Smalltalk and OCaml have lots.  Python
doesn't even have a char type; it just uses strings of length 1.
Rather than having a fixed-size array type, a variable-size
OrderedCollection type, a linked-list type, and a special type for
fixed-size arrays of small integers, Python has a single resizable
array type that it calls "list".  (It also has an immutable tuple
type.) Rather than having a red-black tree type, a hash type, an alist
type, a compact type for many different dictionaries sharing the same
set of keys, a skip-list type, and so on, it has a single dict type,
which it implements with hashes.  (However, like Smalltalk, the
interfaces to these collection types are accessed by sending messages
OO-style, so you can make your own collection type.)

I like this a lot, even though it is one factor in making Python
dramatically slower than Smalltalk.  It makes it a lot easier to get
started with the language, and the speed penalty is often acceptable,
and it makes programs shorter.

(In cases where efficiency is really important, there are extension
libraries that provide other kinds of containers, some of which are
even in the standard distribution; but they are used many times less
frequently than lists.)

This struck me as being somehow analogous to the APL approach; where
APL uses one operator to mean many different (but related) things,
Python uses one data structure to represent many different (but
similar) kinds of collections.  It even uses the same indexing
operator to index into dicts and lists.  PHP, JavaScript, and Lua take
this approach even further, in slightly different directions.

So why do I like Python's data structures being general in this way
and dislike APL's operators being similarly general?  I think it's
because I don't expect Python's data structure overloading to hide
bugs, but only to cost performance, while APL's operator overloading
definitely does hide bugs.

However, there are several cases where Python's data structures are
         fairly strict in ways that help catch bugs, but which are irritating
coming from Perl or JavaScript, and which make your programs bigger:
- There's no implicit conversion between strings and numbers, or
  actually strings and anything.
- Trying to access off the end of an array gives you an error rather
  than returning null or resizing the array.  There are explicit
  'append' and 'extend' methods that make the array bigger.
- Dicts are different from lists, so indexing into a list with a
  number, or trying to append to a dict, gives you an error.  (This
  also helps with efficiency; I vaguely recall that Python's
  predecessor ABC used a single painfully-slow AVL tree for a single
  data structure that served both purposes.)
- Dicts are also different from objects, so accessing an object
  attribute with a run-time name requires hairy syntax.  (Also, this
  namespace separation provides more flexibility for emulating dicts
  with user-defined objects; because Perl doesn't do this, it requires
  a much hairier approach to doing this (called "tie"), which
  I consequently use much less.)

You could imagine that Smalltalk's approach of having different kinds
of collections for, say, fixed and variable-size arrays, might also
catch some kinds of errors, in addition to improving speed; but I
don't think it does.

Reply via email to