Skip to end of metadata
Go to start of metadata

A lot of things are 'rangeable'. Anything that has a logical 'next' to it can be a range. Since you can get an integer from a character by calling int on it, that applies to them as well. Thus, I wrote a char-range function that was promptly (and understandably) denied:

Stu makes some good points here:

  • Performance. I'm not all that great with squeezing every ounce of performance out of things, thus my char-range function is probably the best you'll get from me. I don't know the real performance implications.
  • If many things can be rangeable, they should probably be polymorphic.
  • Are there things that might need to be rangeable in more than one way?

I have some design ideas on of my own, mostly char-range specific:

  • Should char-range's upper bound be exclusive? How intuitive and easy would that be to work with? It would mean that, to get the lower-case alphabet, you'd have to do (char-range \a \\\{) rather than (char-range \a \z). That is awkward and unreadable. Ultimately, it's a difficult decision to make between consistency and usability.
  • char-range should be as flexible as possible. The priority should be on ultimately flexibility and not how easy it is to get a range of lower-case and upper-case letters.
  • Being polymorphic opens a huge can of worms. If we have a Rangeable protocol, none of the range functions can have a no-argument version. I imagine we'd have a 'range' function that would be a wrapper around the protocol. It's possible that, for no argument, this function could default to what the range function defaults to right now, which is a range of numbers from 0 to infinity.
  • Being polymorphic also makes differences in behavior ugly. Inclusive vs exclusive will become a huge problem in all of this. Ruby gets away with this by making exclusive behavior optional with a default argument. We could possibly do something similar.

Sorry for the first example's curly bracket not coming out right. It appears to be impossible to escape it and show a backslash in this markup language. If somebody else can manage it, please do so.

My implementation is of a very Haskell-like range. In Haskell, to get a string of the lower-case and upper-case alphabet, you'd use two ranges.

Prelude> ['a'..'z'] ++ ['A'..'Z']

And following that, my implementation was this:

(defn char-range
  "Returns a lazy seq of chars from start to end
  (inclusive), by step, where start defaults to Character/MIN_VALUE,
  step to 1, and end to Character/MAX_VALUE."
  ([] (char-range (Character/MIN_VALUE) (Character/MAX_VALUE) 1))
  ([end] (char-range (Character/MIN_VALUE) end 1))
  ([start end] (char-range start end 1))
  ([start end step]
     (map char (range (int start) (inc (int end)) step))))

The above Haskell example translated to Clojure using char-range:

user=> (concat (char-range \a \z) (char-range \A \Z))
(\a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z \A \B \C \D \E \F \G \H \I \J \K \L \M \N \O \P \Q \R \S \T \U \V \W \X \Y \Z)

I did a little research into Ruby's ranges, and they appear to be extremely flexible (too flexible?) polymorphic ranges. Documentation is here:

  1. Jan 12, 2013

    Since this topic just got a little play in IRC, here's a reference to Haskell's approach: