Thinking about my "object-oriented equational rewrite rules" and IRC
bots.
> http://lists.canonical.org/pipermail/kragen-tol/2007-March/000855.html

Suppose we think of definitions like

        c = (f - 32) * 5 / 9

as defining properties that exist on every object that meets their
qualifications (and isn't shadowed by some previously-existing c).
Then we could think of something like this:

        todayweather = { f: 95, windspeed: 37 }

as defining a property 'todayweather' that exists on every object.
And then you could plausibly ask, on an object that has only the
properties that all objects have (the Ur-Object), what are the
properties that have a c property?

        c?
                { todayweather: 36.1, normalweather: 25 }

Namespaces
----------

If you're doing this on an IRC bot on a casual channel, you could have
a namespace per nick, and only enumerate properties in your own
namespace.  So there might be [kragen]c and [Isomer]c; but I could
refer to properties from other namespaces explicitly if I wanted to;
simply importing:

        f = [isomer]f

or using without importing:

        c = ([isomer]f - 32) * 5 / 9

Actual Programs
---------------

Let's hold off for now on any way to make these properties (as
opposed to their value on a particular object) first-class in the
language.  But let's have self-quoting keywords, like in Prolog or
Erlang (lower-case), or Common Lisp or Ruby (with colons).  I'm pretty
sure that if I use lower-case for auto-quoting, I'll end up with Very
Important-Looking Programs, so I'm thinking I'll use backquote (`)
instead.

Let's start with a little syntactic sugar for lists.  Let's say that
if we have some semicolon-terminated expressions inside parentheses,
with the last semicolon optional, it is syntactic sugar for a list
made of conses:

        ( a; ) => { x: a, y: `nil }
        ( a; b; c ) => { x: a, y: { x: b, y: {x: c, y: `nil } } }
        () => `nil

Even without first-class properties, we can still write:

        (`nplusone; n).apply = n + 1

such that we can write

        (`nplusone; 3).apply

and get 4.

Given that, we can write map:

        (fn; ()).map = ()
        (fn; {x, y}).map = {x: (fn; x).apply, y: (fn; y).map}

And filter:

        (fn; ()).filter = ()
        (fn; {x, y}).filter = (fn; x; (fn; x).apply; y).filter2
        (fn; x; `t; y).filter2 = { x: x, y: (fn; y).filter }
        (fn; x; `f; y).filter2 = (fn; y).filter

It's pretty brutally obvious that we need some special syntax for cons
here.  Let's try infix @.

        (fn; ()).map = ()
        (fn; x @ y).map = (fn; x).apply @ (fn; y).map
        (fn; ()).filter = ()
        (fn; x @ y).filter = (fn; x; (fn; x).apply; (fn; y).filter).filter2
        (fn; x; `t; y).filter2 = x @ y
        (fn; x; `f; y).filter2 = y
        ().length = 0
        length = 1 + y.length
        x.reverse = ((); x).reverse2
        (a; ()).reverse2 = a
        (a; x @ y).reverse2 = (x @ a; y).reverse2

That works OK, and I could imagine people typing it in IRC.  It would
probably be good to eliminate the argument-order dependencies as much
as possible, and reduce the number of properties defined on all lists
of length two or three.  In the below, {foo, bar} is short for {foo:
foo, bar: bar}, and any free variables are required properties of the
object the property is defined on.

        {list: ()}.map = ()
        map = (fn; list.x).apply @ {fn, list: list.y}.map
        {list: ()}.filter = ()
        {list: x @ y}.filter = {fn, x, include: (fn; x).apply, 
                                y: {fn, list: y}.filter}.filter2
        {include: `t}.filter2 = x @ y
        {include: `f}.filter2 = y
        ().length = 0
        length = 1 + y.length
        x.reverse = { reversed: (), left: x }.reverse2
        {left: ()}.reverse2 = reversed
        {left: x @ y}.reverse2 = { reversed: x @ reversed, left: y }.reverse2

Those aren't quite so brief, but they're still within the length where
people could plausibly type them in a conversation.

So how about strings?  Let's suppose that strings support the
interface of lists of one-character strings, work properly in
pattern-matching, and that one-character strings additionally have a
.ord property that tells you their ASCII code.  Can we split words on
spaces?

        " ".space? = `t
        {ord}.space? = `f
        ({space?: `t} @ y).words = y.words
        ({space?: `f, x} @ y).words = {wletters: (x;), left: y}.word
        {left: {space?: `t} @ rest}.word = wletters.reverse @ rest.words
        {left: x @ rest}.word = {wletters: x @ wletters, left: rest}.word
        {left: ()}.word = (wletters.reverse;)

That's not too bad.  It compares favorably to Scheme in total code
volume:

        (define (words string) (words-of-list (string->list string)))
        (define (words-of-list clst) (map list->string (words-list clst)))
        (define (words-list clst)
          (let ((x (car clst)) (y (cdr clst)))
            (if (char-whitespace? x) (words-list y) (word (list x) y))))
        (define (word wletters left)
          (cond ((null? left) (list (reverse wletters)))
                ((char-whitespace? (car left))
                 (cons (reverse wletters) (words-list (cdr left))))
                (else (word (cons (car left) wletters) (cdr left)))))

The Scheme is 10 lines, 59 words, 498 characters, in place of 7 lines,
47 words, 299 characters.

So from there I think you could quite reasonably, e.g. implement
ternary trees and compute a time-efficient inverted index of a big bag
of strings.  Say, old chat lines, or a small number of HTML documents.

Precedence
----------

When you have more than one rule that's applicable to finding a
property value, you have to decide what to do.  Should you use the
first rule, use the second rule, or combine them somehow?

Aardappel used specificity ordering.  I think you should do the same
here, given that the "source code" is a bunch of people chatting.  But
it would probably be helpful if the specificity ordering were partial,
so that the users would have to resolve potential conflicts manually.

Syntax
------

The question of syntax is a little ugly but probably soluble.  The
normal namespace syntaxes are as follows:

        Syntax          Precedents              Why Not
        isomer/f        Unix                    used for division
        isomer\f        MS-DOS                  painful memories
        isomer.f        C++, Java, Python       used for property access
        isomer:f        MacOS, XML              used for property definition
        isomer::f       C++, Perl               too verbose
        isomer f        Smalltalk               not verbose enough
        [ISOMER]F       VMS                     forgotten
        isomer'f        Ada, Perl4              painful memories, unbalanced '
        isomer$f        R, DCL                  painful memories, ugly

There are some other alternatives, especially if we involve Latin-1:

        isomer|f isomer»f isomer«f isomer§f isomerºf isomer->f
        isomer>f isomer¦f isomer×f isomer÷f isomer¬f isomer®f isomer©f
        isomer±f isomer£f isomer·f isomer=>f isomer=-f isomer:-f
        isomer--f isomer-)f isomer-»f isomer°f isomer*f isomer~f
        isomer!f isomer[]f isomer()f <isomer f> <isomer>f isomer<f>
        isomer_f @isomer.f

Reply via email to