# Some thoughts on prototype programming, transcribed from a notebook
# Notation:
# x.a is the value of the field "a" from the object "x".
# { a = 1, b = 2 } is an object with two fields; equivalent to
# {
# a = 1
# b = 2
# }
# and also { self.a = 1, this.b = 2 }. The field definitions are
# really method bodies; the name before the "." is the name they can
# use to refer to the object they're being called on. You could write
# another object with the same two fields and values as
# { a = 1, banana.b = banana.a + 1 }.
# You can "derive" from an object by writing the object followed by
# { } and some more field definitions. The resulting object will have
# the specified fields overridden. So { a = 1, x.b = x.a + 1 } { a = 3 }
# is another way to write { a = 3, x.b = x.a + 1 }. (In general,
# because of lexical scoping, you can't always evaluate by textual
# substitution in this way.)
# Objects not explicitly derived from anything else are derived from
# "object".
# You can surround something non-wordlike with '' to make it a word
# you can use as a field name or object name. This way you can define
# fields with funny names. For example, { '[EMAIL PROTECTED]@!' = 3 } has one
# field, named "[EMAIL PROTECTED]@!".
# "foo(stuff)" is just a shorthand for "foo{stuff}.'()'". "{a, b, c}"
# is just a shorthand for "{ arg1 = a, arg2 = b, arg3 = c }".
# You can write strings surrounded by "". They derive from an object
# known as "string", which derives from "object".
# In various places here I use infix operators, such as "+", "%",
# "==", "*", and "<". The intent is that "a + b" is shorthand for
# "a.'+'(b)", i.e. "a.'+'{arg1=b}.'()'", but nothing here yet tries to
# override these methods. Most of the operators would be implemented
# as primitives and are self-explanatory, but the "%" on strings is
# not. It's inspired by the Python string "%" operator, but works
# differently; it replaces curly-bracketed names in its left operand
# with the named fields in the right operand.
# "a[b]" is intended to be shorthand for "a.'[]'(b)". Indices are
# zero-based; I've borrowed Python's slice syntax; foo[bar:baz] is a
# subsequence of foo, containing foo[bar], foo[bar+1], foo[bar+2],
# etc., up to foo[baz-1]. foo[bar:] is the same as
# foo[bar:foo.length], and foo[:baz] is the same as foo[0:baz]. I
# don't know how this syntax should work.
# In this file, the names given to self parameters in top-level
# definitions denote the objects on which those names are defined.
# recursive string.split
string.split = {
delim = arg1 = "," # syntax for making "delim" an alias for arg 1
split.pos = string.index(split.delim)
split.before = string[:split.pos]
split.after = string[split.pos + split.delim.length:]
split.recursion = split.after.split(split.delim)
split.'()' = split.pos.ifNull(then=[string], else=[split.before] +
recursion)
}
# ifNull, ifTrue don't need to be primitives
true.ifTrue = {
then = true
else = false
self.'()' = self.then
}
# false's ifTrue is just like true's, except that it overrides the
# return value
false.ifTrue = true.ifTrue { self.'()' = self.else }
# prototype-based functions give us currying, without the mental overhead
object.ifNull = (object is null).ifTrue
# Here's something like C's "for" loop.
# for (state; !done; ...) { ... }
loop = {
state = { i = 0 }
loop.done = loop.state.i == 10
loop.i = loop.state.i + 1
loop.'()' = loop.done.ifTrue(then=loop.state, else=loop(state=loop))
}
# Perhaps it should say "then=loop.state.result" instead of
# "then=loop.state". That could flatten the following routine into
# just a derivation from "loop". I'm not sure what object "loop"
# should be defined on.
string.split = {
delim = arg1 = ","
s.loop = loop(
state = { rv = [], str = string }
loop.pos = loop.state.str.index(s.delim)
loop.done = loop.pos is null
loop.pre = loop.state.str[:loop.pos]
loop.rv = loop.state.rv + [loop.pre]
loop.str = loop.state.str[loop.pos + s.delim.length:]
)
s.'()' = s.loop.rv + [s.loop.str]
}
# I think there's a bug there when pos is initially null
# often we want some object, or if it's null, some substitute. People
# often do this in Perl with 'or' or '||'.
object.nullis = {
default = arg1 = null
nullis.'()' = object.ifNull(then=nullis.default, else=object)
}
# Here's a URL manipulation object. It contains five derivations from
# non-class-like objects, in essentially the places where a
# non-functional OO language would mutate itself.
url = {
scheme = "http"
host = "www.example.com"
port = 80
path = "/"
query = null
fragment = null
username = null
password = null
url.hostport = url.port.ifNull(then=url.host, else="{host}:{port}" % url)
url.auth = url.password.ifNull(
then = url.username
else = "{username}:{password}" % url{username=url.username.nullis("")}
)
url.asString = "{scheme}://{netloc}{path}{qpart}{fpart}" % url{
netloc = url.auth.ifNull(
then = url.hostport
else = "[EMAIL PROTECTED]" % url
)
qpart = url.query.ifNull(then="", else="?" + url.query)
fpart = url.fragment.ifNull(then="", else="#" + url.fragment)
}
url.omitDefPort = url{
port = (url.port == 80).ifTrue(then=null, else=url.port)
}
url.explicitPort = url{port = url.port.nullis(80)}
url.withHostport = {
arg1 = "example.com:8000"
my.hostport = my.arg1.split(":")
my.host = my.hostport[0]
my.port = (my.hostport.length < 2).ifTrue(
then = null
else = my.hostport[1].asInt
)
my.'()' = url{host=my.host, port=my.port}
}
}
# this API allows
url.withHostport("lula:1000").port # --> 1000
url{host="lula", port=80}.omitDefPort.hostport # --> "lula"
# When I wrote this, I thought that a non-prototype-based interface
# probably would need a separate "hostport" class for this kind of
# manipulation, but I don't think that's true.
# first thing I scribbled down in this notation on 2005-07-21
{
fact = {
arg = 1
f.val = arg.equals(0).ifTrue{
then = 1
else = f.arg.times{
f(arg = f.arg.minus(1)
}
}
}
# corrected and modernized, this is
factorial = {
arg1 = 1
f.'()' = (arg1 == 0).ifTrue(
then = 1
else = f.arg1 * f(f.arg1 - 1)
)
}
# ok, so here we go again with string.split
object.loop = {
prev = { i = 0 }
loop.i = loop.prev.i + 1
loop.done = loop.i == 10
loop.'()' = loop.done.ifTrue(then=loop.prev.result, else=loop(prev=loop))
}
string.split = string.loop {
delim = arg1 = ","
self.prev = self { rv = [], left = string }
loop.pos = loop.prev.left.index(loop.delim)
loop.done = loop.pos is null
loop.rv = loop.prev.rv + [loop.prev.left[:loop.pos]]
loop.left = loop.state.str[loop.pos + loop.delim.length:]
loop.result = loop.rv + [loop.left]
}
# compare Python more-or-less equivalent:
# def splitstring(string, delim=","):
# rv = []
# left = string
# while 1:
# pos = left.index(delim)
# if pos == -1: return rv + [left]
# rv.append(left[:pos])
# left = left[pos + len(delim):]
# ... not much of an improvement.
# In "contextual" form, where most of the self-names are elided:
split = loop {
delim = arg1 = ","
self.prev = self { rv = [], left = string }
pos = prev.left.index(delim)
done = pos is null
rv = prev.rv + [prev.left[:pos]]
left = state.str[pos + delim.length:]
result = rv + [left]
}
# string.index finds one string in another; the naive algorithm:
string.index = string.loop {
needle = arg1 = "foo"
self.prev = self { i = 0 }
outer.found = string.loop (
prev = self { i = 0, equal = true }
in.equal = string[in.i + outer.i] == outer.needle[in.i]
inner.done = (!inner.equal) or inner.i >= outer.needle.length
)
outer.done = outer.found or outer.i > string.length - outer.needle.length
outer.result = outer.found.ifTrue(then=outer.i, else=null)
}
# typedef struct { char *s; int length; } string;
# int index(string *haystack, string *needle) {
# int ii, jj;
# for (ii = 0; ii <= haystack->length - needle->length; ii++) {
# for (jj = 0; jj < needle.length; jj++) {
# if (haystack->s[ii + jj] != needle->s[jj]) goto nextloop;
# }
# return ii;
# nextloop:
# }
# return -1;
# }