Sun - August 12, 2007
Andy Ihnatko is one funny dude—and not Fake Steve
I actually find it a little tiring to read a
whole
article of his because of the energy burned up being
amused.
Posted at 06:47 PM
Sun - April 8, 2007
Donuts filled with gelatinous goop instead of jelly: Evil or just bad
taste?
Discuss.
Posted at 11:46 PM
Sun - December
24, 2006
“Brain the size of a planet, and they want me to run software from
the 1950’s.”
Marvin the Paranoid Android would probably
get all sulky if he were a web server.
Today, while attempting to create an account
on a website, I got this not-so-helpful
reply:
“The screen name you
requested is too short, too long, or contains illegal
characters.
Screen names must be between 2 and 24
characters long and can contain only letters and numbers (no
spaces).”
Well, don’t
keep me guessing, which is it? Too short? Too long? Does it contain spaces or
accents that are known to make computers explode in a shower of sparks while
repeating “does not compute” in a voice suspiciously similar to that
of Lwaxana Troi?
Clearly, some
human wrote code to test for each of these failures individually, so why
didn’t they report which specific test (or tests) failed, instead of
requiring me to waste time performing the same tests so I can figure out how to
change my desired screen name to fit in its puny electronic
brain?
Of course, the larger issue
is that these limitations are almost entirely unnecessary. Sure, some finite
restrictions on the length make sense; however, twenty four characters is
shorter than many real-world names, and both computers and humans are entirely
capable of handling multiple words, accents and other letters and symbols from
the world of written communication. Not since perhaps the 1970’s have
these limitations been widely required by computing systems. These sorts of
restrictions are artificial and should be relegated to history before
extraterrestrials finally do show up to visit and we have to embarrassingly
explain why we need them to spell their names with fewer than twenty four
letters from the Roman alphabet.
I
still occasionally come across forms both electronic and paper that force me to
truncate my first name, Christopher, to an annoying ten-character
“Christophe”. I guess
Christopher
is one of those whacky names from the 1960’s that people just don’t
use much.
Posted at 05:02 AM
Effective Dylan: 50 Specific Ways Dylan is Easier to Use Than C++
A point-by-point comparison between Dylan
and the fifty items described in Scott Meyers' Effective C++.
I’ve been meaning to do this for a
while. Here I go. The following is a brief† explanation of how Dylan
compares against all fifty items listed in Scott Meyers’
Effective C++: 50
Specific Ways to Improve Your Programs and
Designs††. Much as
I like this book and find it useful for programming in C++, the fact is that a
lot of what’s in it (and other books on C++) is about the unnecessarily
hard parts of C++. Dylan generally provides a simpler and more productive (and
enjoyable!) approach to
programming.†I wrote that
before writing the bulk of this. Since there are, after all,
fifty
points, the overall result isn’t very short. However, my response to each
item is relatively brief and may leave out a number of details that would be
worth expanding on in the
future.††Note that I
used the first edition of Scott’s book, although that link above points at
the second edition on Amazon. At some point I’ll go through the second
edition and update my comments if
necessary.Shifting from C
to C++This section is very
specific to C vs. C++, so there isn’t a direct comparison in Dylan, but
I’ll add some comments
anyway.1.
Use const and inline instead of
#define. Dylan has no preprocessor or
#define. It has constants and inline
functions.2.
Prefer iostream.h to
stdio.h. Dylan has stream-based I/O
libraries.3.
Use new and delete instead of malloc and
free. Dylan has automatic memory
management. You do not need to call functions to allocate and deallocate
memory.4.
Prefer C++-style
comments. Dylan supports both styles of
C++ comments (/* */ and //), and common Dylan programming style agrees with
Meyers.Memory
ManagementMost of the items
in this section are non-issues in Dylan, which has automatic memory management.
There are no new and delete to use
incorrectly.5.
Use the same form in corresponding calls to
new and delete. An impossible error to
make in Dylan. If you create a collection object, every object in it will be
automatically deallocated as appropriate when the collection is
deallocated.6.
Call delete on pointer members in
destructors. Another detail that’s
impossible to overlook in Dylan. All objects are properly deallocated when no
longer referenced.7.
Check the return value of
new. Dylan’s equivalent to new,
“make,” signals an exception if object allocation or initialization
fails. It is not possible to unintentionally overlook a
failure.8.
Adhere to convention when writing
new. There is no
new.9.
Avoid hiding the global new.
There is no global
new.10.
Write delete if you write
new. There is no…rule number
six.Constructors,
Destructors, and Assignment
OperatorsThese issues are
dramatically simpler in Dylan. Automatic memory management eliminates the need
to define constructors and destructors when the only resource being managed is
memory. Initialization is much simpler in Dylan and in most cases can be defined
in the class definition without a separate initialization function. There is no
assignment operator function, and, in contrast to C++, Dylan doesn’t copy
objects implicitly, and explicit copying is
rare.11.
Define a copy constructor and an assignment
operator for classes with dynamically allocated
memory. Although you can define the
equivalent of copy constructors when desired in Dylan, automatic memory
management makes it unnecessary to worry about writing copying code merely to
properly manage memory. There is no copying assignment operator in Dylan, and no
implicit copying of objects, so it is rare that you need to write copying code;
you do not need to do so simply to satisfy language semantics as in C++, you
only need to do so when you wish to support explicit
copying.12.
Prefer initialization to assignment in
constructors. This actually applies to
Dylan, although there’s less of a chance a Dylan programmer will make a
mistake here. For most cases, object initialization can be expressed just once,
in the class definition, unlike C++ initialization clauses, which cannot be used
in as many cases and which have to be diligently written for every constructor.
Dylan only requires an explicit initialization function for special cases, e.g.,
when multiple initial values have
interdependencies.13.
List members in an initialization list in
the order in which they are declared. A
non-issue in Dylan, where initial values are given in the definition of each
member (called “slots” in Dylan), so there is no way to get the
order wrong.14.
Make destructors virtual in base
classes. A non-issue in Dylan, where all
functions are implicitly “virtual” (besides, Dylan does not have
destructors in the C++ sense.) Note that this does
not
mean all function calls incur dispatching overhead, a cost many C++ programmers
are very conscious of. If a given call site doesn’t require dynamic
dispatch, it automatically becomes a simple function call, just as with
non-virtual C++ member functions, and may even be inlined as appropriate. There
is no need (or means) to explicitly declare “virtual” and
“non-virtual” functions, and no class definition changes are ever
necessary if program requirements later
change.15.
Have operator= return a reference to
*this. There is no assignment operator in
Dylan.16.
Assign to all data members in
operator=. There is still no assignment
operator in Dylan.17.
Check for assignment to self in
operator=. Well, there was one, but the
cat’s eaten it.(Someone
asked me for further clarification: There is an “assignment
operator” in Dylan, of course. It looks just like Pascal’s
“:=”. However, it is not a function. It is not equivalent to
C++’s operator=, which performs copying, and there is no need to implement
one for your classes as in C++ simply to avoid problems with C++’s
pervasive implicit
copying.)Classes and
Functions: Design and
DeclarationMeyers’ has
some good things to say about class and function design that, broadly speaking,
apply as much to Dylan as they do to any other language. However, a lot of these
items are rendered simpler—or in some cases, trivial—to deal with in
Dylan.18.
Strive for class interfaces that are
complete and minimal. Okay, Scott, will
do. In fact, in Dylan, class definitions are usually much simpler than in C++,
only including definitions of slots (“data members”) and
(implicitly) their accessors—an approach Scott recommends
for C++, but which is made more difficult to adhere to than in Dylan
because of the limitations of non-member
functions.19.
Differentiate among member functions,
global functions, and friend functions.
Dylan has a simpler, more orthogonal programming model in which there is
essentially only one kind of function (called a “generic function”),
which is polymorphic, similar to a C++ virtual member function. Generic
functions do not “belong” to classes, and there is no real
distinction between “member” and “non-member”, or
“friend” functions, making it simpler to evolve programs with fewer
and more localized source changes. You use explicit modules (aka
“namespaces”) to control access and encapsulation instead of
implicit class namespaces and public, private, protected, and friend
declarations.20.
Avoid data members in the public
interface. In Dylan, accessor functions
are automatically defined for all slots. In fact, there is no way to explicitly
access a slot “directly” as in C++. All slot access is via accessor
functions (calls to which are often optimized away in practice). In C++, you
have to explicitly write your own accessors, complicating the code and
increasing the likelihood that someone will avoid that work and make a data
member public when they shouldn’t, and thereby make the code harder to
maintain and evolve.21.
Use const whenever
possible. Dylan doesn’t have const,
which isn’t as clearly necessary as in C++. One reason for this is that,
just as functions can accept multiple arguments, in Dylan they can also return
multiple values, so you never need to design functions that take
“output” pointer/reference arguments, and so there’s less
need to differentiate them from “input” arguments. Dylan functions
also tend to be functional rather than mutating (ie., they are implicitly
const). Although Dylan explicitly supports imperative programming, most standard
library functions do not alter their arguments, and so const-like declarations
would often be redundant. (This is not to say there would be no value in having
something like
const
in Dylan, but it isn’t as much of a clear win as in
C++.)22.
Pass and return objects by reference
instead of by value. Dylan always uses
reference semantics. There is no direct way to pass by value, although you can
explicitly copy an object then pass the copy
along.23.
Don’t try to return a reference when
you must return an object. See #22. This
simply isn’t an issue in
Dylan.24.
Choose carefully between function
overloading and parameter defaulting.
This applies only indirectly to Dylan. First, it’s important to note that
Dylan does not have function overloading in the same sense as C++. Second,
Meyers recommends overloading when there is no reasonable default value for an
argument, but in Dylan it is always possible to define a default value (using
type-unions, which I won’t go into here), and furthermore, it is possible
to tell whether an optional argument was passed to a Dylan function, obviating
the need for a default value if avoiding one is
desired.25.
Avoid overloading on a pointer and a
numerical type. In Dylan there are no raw
pointers and therefore no null pointer, but more to the point, Dylan is more
type-safe than C++ and there is no way to confuse a zero with a null pointer, or
in fact any number with any value that is not a number. In stark contrast to
C++, Dylan’s false, zero, and null character ('\0') are of distinct types,
and there is no implicit casting between them as in
C++.26.
Guard against potential
ambiguity. There is no implicit casting
and no ambiguity between construction and conversion; you have to explicitly
invoke the desired function. Generic functions do not “belong” to
classes in the same sense that C++ member functions belong to their classes, and
so the potential ambiguity between two inherited member functions with the same
name doesn’t have a direct analogue in Dylan. The closest match is name
collisions in modules (again, aka “namespaces”); similar to C++, you
do need to resolve any collisions that occur during importing, by excluding or
renaming one or more of the offending
names.27.
Explicitly disallow use of implicitly
generated member functions you don’t
want. The example Meyers uses is the
default assignment operator. Dylan has no assignment operator, so that
specifically is a non-issue. More generally, Dylan doesn’t automatically
define any functions on your behalf that you would need to suppress (any
undesirable behaviors are up to you to implement
yourself).28.
Use structs to partition the global
namespace. Here Meyers is suggesting a
short-term workaround for the lack of namespaces in C++. Now that modern C++
compilers support namespaces, this is unnecessary. As mentioned above, Dylan has
namespaces (called
“modules”).Classes
and Functions:
ImplementationSome of these
points discuss design issues of a general nature that tend to apply across
languages, but others highlight where C++ is more complex than one might desire,
where Dylan provides a simpler and more enjoyable programming experience in
contrast. In particular, Dylan has no header files and instead relies upon
whole-program analysis, where the compiler can see all the source code for a
program or library and isn’t constrained by a monolithic,
compilation-order-dependent mechanism like C++’s translation
unit.29.
Avoid returning “handles” to
internal data from const member
functions. As a general point, this
applies to Dylan, and I’d modify this to the more broad, “avoid
exposing implementation information through interfaces.” Note, however,
that Dylan has no raw pointers, and that all slots are implicitly accessed via
functions, so less raw data is exposed by
default.30.
Avoid member functions that return pointers
or references to members less accessible than
themselves. This applies to Dylan in the
same general way that #29 does, though, again, there is no way to return a raw
pointer or reference to a member, making this (at least slightly) less of an
issue.31.
Never return a reference to a local object
or a dereferenced pointer initialized by new within the
function. You can blatantly ignore this
in Dylan, where it’s perfectly safe to return any object, since
you’re always using heap semantics (ie., there is no way to return a
reference a stack object). Note that I said heap
semantics.
Dylan programs are written as though objects are always allocated on the heap,
but objects may be allocated on the stack if there are no external references to
them. Dylan treats the location of an object as an implementation detail, and
allocating on the stack is an optimization. Automatic memory management
eliminates the problems seen in C++ due to incorrect references or omitted
deletion.32.
Use enums for integral class
constants. The opposite is true in Dylan:
Go ahead and use constants, that’s what they’re for. The Dylan
compilation model doesn’t use headers. Instead, the compiler simply looks
at the definition of a constant regardless of where it is in the sources to find
its value. So, Dylan doesn’t have the same scoping and compilation order
problems that C++ does.33.
Use inlining
judiciously. [Curly voice] Why, soitenly.
In fact, Dylan compilers make judicious use of inlining on your behalf in many
cases, and, again, Dylan doesn’t use headers, so you don’t have to
place function bodies in headers to get them inlined. The compiler can inline
any function in your program as appropriate, making inlining easier to manage
with fewer changes to source code. Dylan compilers are also expected to perform
certain kinds of inlining and partial inlining that you might not expect of a
C++ compiler (or that might not be possible due to the language semantics and
compilation model).34.
Minimize compilation dependencies between
files. Of course, reducing dependencies
is a good thing, but again, since there are no headers, the task is made much
simpler in Dylan. No implementation details are explicitly exposed in header
files. Only those dependencies that actually occur in your programs need cause
recompiles, and only the affected functions need to be processed, rather than
recompiling everything in a translation unit that directly or indirectly may
depend upon a header file, as in
C++.Inheritance and
Object-Oriented DesignSome of
these items are general and apply to Dylan, but others are, again, non-issues in
Dylan, or in fact the opposite of common Dylan practice. C++ imposes some design
constraints that Dylan programs are not subject
to.35.
Make sure public inheritance models
“isa.” Dylan doesn’t
have private inheritance, so you can ignore some of what Meyers has to say here,
but the general point about inheritance modeling “isa” still
basically applies to Dylan (and in fact, to all OO
languages).36.
Differentiate between inheritance of
interface and inheritance of
implementation. A good idea in Dylan,
too. Meyers goes on at length on this topic, and the summary is that Dylan is a
bit simpler here because there are no non-virtual member functions, nor pure
virtual member functions. To define an abstract class in Dylan, you do so
explicitly with the adjective “abstract” in the class definition;
there is no need to define a pure virtual member function merely to implicitly
make a class uninstantiable.37.
Never redefine an inherited nonvirtual
function. An impossibility in Dylan, as
all functions use “virtual” semantics. This makes it simpler to
modify and maintain Dylan programs without breaking client
code.38.
Never redefine an inherited default
parameter value. In Dylan it is perfectly
fine to do so, whereas C++ semantics make this problematic. Subclasses can
impose further restrictions and therefore it is desirable to alter default
initialization values, and Dylan semantics make this trivial to
do.39.
Avoid casts down the inheritance
hierarchy. Because it is a dynamic
language, Dylan does not have or need up- or down-casting. Furthermore, it does
not have raw pointers or non-virtual functions, eliminating most of the issues
raised by Meyers here. As he suggests, though, you should avoid relying upon the
exact type of an object and instead provide one or more polymorphic functions
that provide the desired tests for object attributes (in this case, the example
is testing an object’s class, but this rule can be applied more
generally).40.
Model “has-a” or
“is-implemented-in-terms-of” through
layering. Generally applicable to Dylan,
too.41.
Use private inheritance
judiciously. There is no private
inheritance in Dylan, so this is a non-issue. More specifically, Meyers points
out some cases where C++ requires using private inheritance instead of the more
desirable layering approach. In Dylan, these cases simply do not occur, and
layering is always applicable.42.
Differentiate between inheritance and
templates. Dylan directly supports
generic programming without the use of a specialized syntax like C++ templates,
and it is considered good form to make the difference transparent. Most Dylan
programming is generic programming to at least some degree, making it easier to
reuse code. Dylan also supports homogenous and heterogeneous containers. In
fact, unlike in C++, neither of these features requires generating duplicate
code (although this can be done as an optimization, as a form of
inlining).43.
Use multiple inheritance
judiciously. Taking the topic title
literally, it also applies to Dylan. However, MI in C++ has a number of
complications that MI in Dylan simply doesn’t suffer from, making MI use
easier and more common. Mixin inheritance is a common idiom in
Dylan.44.
Say what you mean; understand what
you’re saying. This is sort of a
summary of some of Meyers’ previous points. I won’t bother going
into detail. If you read his book and my comments, it should be straightforward
to see how his points apply to
Dylan.MiscellanyThese
are general points, for the most part, and apply to Dylan, although the details
differ.45.
Know what functions C++ silently writes and
calls. Speaking very,
very
broadly, this is good advice for Dylan, too. However, in stark contrast to C++,
Dylan writes and calls very little code implicitly, and any implicit code does
the right thing (whereas, for example, in C++, if you have any pointer data
members the default destructor will not call delete on them). In fact, Dylan
really only generates accessor functions for slots, and they’re trivial.
Default methods on make() and initialize(), which create and initialize objects,
do the right thing by default. There is no copying assignment operator, copy
constructor, or address-of operators as generated in
C++.46.
Prefer compile-time and link-time errors to
runtime errors. The same is true of
Dylan, although unlike C++ you are guaranteed to get runtime errors (exceptions)
instead of silent failures for things that can’t be checked at compile- or
link-time.47.
Ensure that global objects are initialized
before they’re used. This is
guaranteed in Dylan. All globals (and locals, too) must have an explicit initial
value. There is no way to forget to initialize one. Furthermore, Dylan makes it
easier to specify initial values, and it guarantees that globals with
dependencies will be initialized in the correct
order.48.
Pay attention to compiler
warnings. A good idea no matter what
language you’re using.49.
Plan for coming language
features. Most of Meyers’ points
here are very specific to C++. I’ll just point out that one of
Dylan’s greatest strengths is that it makes it easier to change
things.50.
Read the
ARM. Read the Dylan Reference
Manual. It’s shorter and simpler than the ARM. To be fair, a lot
of the “motivation” discussion in the ARM won’t be found in
the DRM. Instead, look to other sources, such as info-dylan
email and comp.lang.dylan
Usenet archives, as well as Dylan
Programming.
Posted at 05:00 AM
Sun - September 17, 2006
The Libration of the Earth’s Moon
Wikipedia describes libration and
includes a fascinating (simulated) animation of the moon as seen from the Earth
over a one-month period. The surprising thing to me is how much the visibility
of the moon’s surface varies over a month. Due to actual movements of the
moon and changes in the angle of observation, 59% of the moon’s surface
can be seen from Earth, not merely a strict
“side”.I find the
observed movement of the moon in the animation almost disturbingly wobbly for
such a large body (though it doesn’t actually move quite that
much.)
Posted at 01:21 PM
Sun - August 6, 2006
A Handy CSS Debugging Snippet
I use the following bit of CSS to help
visualize the structure of an XHTML (or HTML) document by putting a colored
outline around the border of every element. At each level in the hierarchy the
color changes so you can see when “depth”
changes.
* { outline: 2px
dotted red } * * { outline:
2px dotted green } * * * {
outline: 2px dotted orange
} * * * * { outline: 2px
dotted blue } * * * * * {
outline: 1px solid red } *
* * * * * { outline: 1px solid green
} * * * * * * * { outline:
1px solid orange } * * * *
* * * * { outline: 1px solid blue
}
I usually keep this block
of rules at the top of a stylesheet, commented out with /*…*/, which I
remove when I want to see the structure.
Posted at 09:50 AM
Sat
- August 5, 2006
The origin of “chortle”
The word “chortle” was coined by
Lewis Carroll in Jabberwocky.
Posted at 12:37 PM
Tue - October 11, 2005
Generic Programming is Simpler in Dylan vs. C++
Dylan programs are usually clearer and
easier to understand than their C++ equivalents, especially when writing generic
code.
The following C++ examples are from Thomas
Themel’s blog. He has some interesting things to say about
programming languages there, but I'll let you read what he has to say. I’m
just using his code as a starting point for some of my own
ramblings.The original C++
example: void
exchange(map<string, int>& ref, int id1, int
id2) { if
(ref.find(id1) != ref.end()
&& ref.find(id2)
!=
ref.end()) { string
tmp =
map[id1]; map[id1]
=
map[id2]; map[id2]
=
tmp; } }The
C++ template version, which is more
general: template
<typename _id_t, typename _mem_t>
void
exchange(map<_id_t, _mem_t>& ref, const _id_t& id1, const
_id_t&
id2) { typename
map<_id_t, _mem_t>::iterator it1 =
ref.find(id1); typename
map<_id_t, _mem_t>::iterator end =
ref.end(); if
(it1 !=
end) { typename
map<_id_t, _mem_t>::iterator it2 =
ref.find(id1); if
(it2 !=
end) { _mem_t
tmp =
it2->second; it2->second
=
it1->second; it1->second
=
tmp; } } }One
of the problems with C++ is that if you want to write general, flexible code, it
can get pretty hairy, making it more difficult to understand—or to write
the code correctly in the first place. Similar Dylan code is usually much
clearer and succinct.A simple
Dylan
rendition: define
method exchange (c :: <collection>, key1, key2) =>
() let
temp1 =
c[key1]; let
temp2 =
c[key2]; c[key1]
:=
temp2; c[key2]
:=
temp1; end
method exchange;Perhaps the
most important thing to note here is that this Dylan rendition is equivalent to
the template version, because it can work with arguments of several different
types, yet, like the original, non-template C++ code, it remains clear and easy
to read, where C++ template syntax can get quite verbose and
obscure.In fact, it is more
general than the template version, because it works with any collection, not
just maps. If you wanted to limit it to “maps”, you would just
change
“<collection>”
to
“<table>”,
the Dylan hash-table
class.Similarly, because we
didn’t restrict the types of key1 and key2, they can be any type at all.
So this function can work with arrays, hash-tables, linked-lists—any
collection type that can be accessed with the
“[]”
operator—and they can contain elements of any type, without the consequent
verboseness you’re likely to find in an equivalent C++
template.Now, you may have
noticed that this isn’t exactly a direct translation from the original:
Unlike the C++ code, this signals an error (“throws an exception”)
if key1 or key2 aren’t found. I don’t understand why the original
version silently ignores invalid arguments. Seems like a bad idea to me. But,
this is a good opportunity to discuss how to handle invalid keys in
Dylan.First, here’s a more
direct translation of the original. It only accepts hash-tables, and it checks
whether the keys are valid before using them, so invalid keys are silently
ignored: define
method exchange (t :: <table>, key1, key2) =>
() if
(key-exists?(t, key1) & key-exists?(t,
key2)) let
temp =
t[key1]; t[key1]
:=
t[key2]; t[key2]
:=
temp; end
if; end
method exchange;Again, this
is just as general as the C++ template function, but without the complicated
syntax. In Dylan, all code is effectively template code, it’s just a
matter of degree. The more tightly you define the types used in your programs,
the less generic—and the less template-like—your programs are. In
that vein, for the rest of this discussion I’ll go back to using
<collection>
instead of
<table>.Explicitly
checking whether the keys are valid could be a waste of effort if the keys are
valid most of the time. When we attempt to access the collection the keys will
be tested, anyway. Instead, we can just try to use the keys and establish an
exception handler that does nothing if one or both are
invalid: define
method exchange (c :: <collection>, key1, key2) =>
() block
() let
temp1 =
c[key1]; let
temp2 =
c[key2]; c[key1]
:=
temp2; c[key2]
:=
temp1; exception
e end
block; end
method
exchange;(“block…exception”
is like C++’s
“try…catch”.
In fact, the C++ code could have used a similar
approach.)Now, Dylan actually has
a more efficient, direct way to look up collection elements without the cost of
testing the keys or handling an exception. Just as in C++, when you use the
array access operator
“[]”,
you’re actually calling a function
(“operator[]”
in C++), and you can explicitly call that function instead of using the
“x[i]”
syntax. In Dylan, that function is named
“element”,
and when you call it explicitly you can give it additional
arguments.element
accepts an optional keyword argument called
“default:”,
and if the key is invalid, it returns the default value instead of signaling an
error. This means that the key is only tested for validity once, in
element,
and an invalid key produces an identifiable result instead of imposing the cost
of signaling and handling an error. This is more efficient in cases where you
expect invalid keys to occur
often: define
method exchange (c :: <collection>, key1, key2) =>
() let
tmp1 = element(c, key1, default:
#f); let
tmp2 = element(c, key2, default:
#f); if
(tmp1 &
tmp2) c[key1]
:=
tmp2; c[key2]
:=
tmp1; end
if; end
method exchange;Here,
we’ve used
“#f”,
the literal for boolean false, as the default value. It’s quite common in
Dylan code to use false to mean “none of the above” or “no
value”, since it’s convenient to test for. Because Dylan is a
type-safe language, false will never be confused with any other value, such as
zero (or the empty list, as in Lisp). Dylan does not have a
NULL
or
Nil,
which are not type-safe. (In C++, you are also encouraged to use specific,
type-safe values instead of
NULL
or
0.
In fact,
“NULL”
is explicitly not an official part of C++, though many programmers still use
it.)Although it’s common to
use false as a default value for
element(),
using it means that the above code isn’t appropriate if we want to allow
the collection to contain false as a value. To make sure we can distinguish
between “the element is #f” and “there is no element with that
key”, we can construct a unique object that will never be stored in the
collection: define
constant $unfound =
make(<pair>);(The
exact class doesn’t matter much, but a
<pair>
is probably the simplest built-in class in Dylan. It’s used to build
linked lists and other data
structures.)Now, we can use it as
the default value for
element: define
method exchange (c :: <collection>, key1, key2) =>
() let
tmp1 = element(c, key1, default:
$unfound); let
tmp2 = element(c, key2, default:
$unfound); if
(tmp1 ~== $unfound & tmp2 ~==
$unfound) c[key1]
:=
tmp2; c[key2]
:=
tmp1; end
if; end
method
exchange;(~==
is the “not the same object as” comparison operator. We’re
testing whether the result of
element()
is the object stored in
$unfound,
not just whether it is equal to the object’s
value.)In fact, although this
isn’t part of the language defined in the Dylan Reference
Manual,
$unfound
is defined in a common set of extensions to the standard library, allowing us to
omit the “define
constant”
above.I’ve taken this
opportunity to discuss several different points about Dylan and about
programming in general, but I hope the take-away for you is that Dylan code can
be generic while still being clear and easy to understand, where achieving the
same thing in C++ may not always be
possible.(Disclaimer: I didn't
compile any of the example code, but I believe it’s all
correct.)[Later, I noticed and fixed
some minor typos.][Later later, I
changed
~=
to
~==
and added the note that explains the operator.]
Posted at 05:38 AM
Sat
- March 5, 2005
FedEx is like freakin’ elves
I swear they’re like elves that come
in the night to make shoes or something; they come and go without you seeing
them.
FedEx is like a bunch of little elves or
sumthin’. I checked on a package I’m expecting and the tracking site
said it had been loaded onto the delivery vehicle at 8:00 AM yesterday. At
first, I figure I’m not reading it right. It must be due tomorrow, because
I didn’t see FedEx show up today. So, I decide just now to go check the
front door, and sure enough there’s a little “we missed you”
sticker on the door.
I left the
house a few minutes after the time on the notice. I’ll be generous and
assume it’s due to clock inaccuracies (mine, theirs, or both o’arn),
but I swear they’re like elves that come in the night to make shoes or
something; they come and go without you seeing
them.
“We missed you.”
Well, FedEx, I missed you, too.
Posted at 01:00 AM
Sat
- December
25, 2004
53a50n’5 Gr33+1ng5, right back atcha
Because programmers can’t do
anything in the usual way.
Scott Knaster wrote a bit of holiday
code in his blog. Because true geek humor also demands a
(half-)serious response, here’s my rendition, “translated”
into Dylan for
comparison:let
seasons-greetings
= if
(you.do-christmas?) #"merry-christmas" else #"happy-holidays" end;let
also = make(
<happy-birthday>, to:
scott-knaster, date:
26, month:
$december );
Posted at 11:52 PM
Tue - December
7, 2004
Characters: How abstract should they be?
Some thoughts on whether to model
characters as abstract characters or as characters in a particular character set
or encoding.
Recently in the #dylan IRC channel on freenode.net there was a discussion
about how to implement characters in computer programming languages as abstract
characters—characters that exist outside of any particular encoding or
character set, which you can map to and from character values in particular
character sets and encodings—with the goal of representing all character
data as abstract characters by default. Here are some of my thoughts from that
discussion, lightly edited:Every
time this discussion comes up, a sticking point for me is that you have to use
something
as a “handle” to get to an abstract character, and that usually
involves an unacceptable amount of storage overhead. The popular suggestion is
to use a unique string or “symbol”, sometimes known as an
“interned” or “uniqued”
string.I’ve always
preferred approaches where you don’t have abstract characters. Every
character is in some character set and you can map between them, and you either
pick some relatively comprehensive intermediate encoding like Unicode for
performing those mappings or you require pairs of mappings to be defined for
combinations of character sets. In this model, character instances occupy at
most a single machine word, just like an integer, and there is little or no
auxiliary information about characters stored elsewhere. (By the way, mappings
don’t have to be defined for every possible combination; you can chain
them together to save space.)The
interface to characters is still somewhat abstract in this approach, but if
there’s an intermediate encoding for mapping, you can get a speed boost by
converting your character data to that encoding and using it to do most of your
text processing.At least, ten
years ago it would have been unacceptable to have a symbol for every assigned
Unicode code point stored on disk or in memory. Today, I am still reticent to
impose an overhead of hundreds of kilobytes or potentially megabytes just to do
something as common as work with text in a way that doesn’t require
higher-level facilities like spelling checkers and
hyphenation.For any abstract
character proposal, I want to see hard performance, size, and deliverable size
numbers, as well as a comprehensive description of the target users. Making this
a part of the core of a programming language means it would affect
every
program.I think the overriding
question to ask is, what’s the value of having truly abstract characters?
Do we really need them, or is it just a conceptually pure model that has an
aesthetic appeal? I don’t see much you can do with them besides test for
equality and convert them to/from other character sets (you can’t compare
them for sorting, you can’t convert them to/from integers, and you
can’t (directly) convert digit characters to numerical values, for
example).I think we can provide a
protocol that’s “abstract enough”, but uses a more concrete
representation.An issue for me is
that I think characters should be more like integers in that they have very few
properties that the core language has to support directly. We don’t talk
about storing numerical properties for integers. Even if we support Unicode
source code, character and string literals, and some Unicode character
properties, we don’t necessarily need to support
all
of Unicode or any other non-trivial character system. Leave that to additional
libraries that applications can pick and choose
from.You might say I prefer
characters that are abstract in that they have little or no implementation
overhead (speed or space), only the minimum of functionality necessary to
support simple text processing, and that relegate most higher-level
functionality to (optional) libraries. I realize that we may only be discussing
where to draw that line.It also
just occurred to me that I think trying to model abstract characters is the same
as trying to model abstract numbers. That’s a very high-level thing to try
to do that’s probably best left to optional libraries. The size and speed
efficiency of integers and floats that are close to the hardware is hard to
ignore, and it’s highly desirable for characters to have the same
efficiency advantages.
Posted at 05:46 AM
Sat
- November 20, 2004
Dylan Object Copying (or lack thereof)
Answering the question “How do you
copy objects in Dylan?” often begins with “You
don’t.”
[Update: Pete Gontier pointed out that I
used the term “bitwise copying” when describing what C++ does to
copy objects by default, but in fact the correct description is
“member-wise copying”. This is a common terminology mistake, and the
difference is important; C++ invokes a copy-constructor to copy each member and
its members, recursively. Of course, when there are no explicit copy
constructors to invoke, a C++ compiler could optimize it into a bitwise copy
when appropriate. I've revised the text below to use
“member-wise”.]Recently
there was a discussion in the #dylan IRC channel on freenode.net about copying objects in
Dylan. Someone asked how to copy objects as in C++, where assignment with
‘=’
is a copying operation and when the destination is a class (or struct) instance
the compiler can automatically generate a simple member-wise copy of its data
members.In short: Dylan programs
copy objects much less often than typical C++ code, assignment does not copy
objects, objects are only copied explicitly, and simple member-wise copying is
often inappropriate.There are
four (no, three, Sir!) points to
observe:1. In Dylan, assignment
to a binding (a global or local variable, or a function parameter) does not copy
or construct an object, it merely makes that binding refer to the source object.
Compared to C++, it is as if every variable were a pointer to a heap-allocated
object, so assignment merely copies the
pointer. let obj1 = make(
<my-class> ); let
obj2 = obj1;Here, both
obj1 and
obj2
refer to the same object. The ‘==’ operator tests whether two values
are the same object, and returns true
(#t) in
this case: obj1 ==
obj2; ⇒
#tIf we change any slots of
obj1 or
obj2,
both bindings will see the same
values: obj1.my-slot :=
42; obj2.my-slot; ⇒
42This is analogous to the
C++ code: my_class* obj1 =
new my_class; my_class*
obj2 =
obj1; obj1->my_slot =
42; obj2->my_slot; ⇒
422. Since Dylan always uses
reference semantics (every binding is like a pointer to an object), there is no
implicit copying of objects as there is in C++. For example, C++ copies objects
to create temporaries while evaluating an expression, and when passing values in
and out of functions. In contrast, objects are only copied explicitly in Dylan,
and copying is performed much more rarely than in
C++. my_class
obj1; my_class result =
some_function( obj1 );In the
above C++ example,
obj1 is
copied when passed to
some_function()
and the result of that function is copied to
result,
because C++ is using pass-by-value semantics here. In the Dylan version, no
copying occurs: let obj1 =
make( <my-class>
); let result =
some-function( obj1 );The
equivalent C++ code
is: my_class *obj1 = new
my_class; my_class *result
= some_function( obj1 );3.
Copying is generally accomplished either by calling
shallow-copy(),
which is roughly like a C++ copy constructor, or by simply creating a new object
with
make()
and passing in initial values taken from a source object, which is like a more
general C++ constructor that takes additional
parameters. let obj2 =
shallow-copy( obj1 ); let
obj3 = make( <my-class>, foo: obj2.foo, bar: obj2.bar
);Here, we pass the
foo and
bar slots
of obj2
to make()
using keyword arguments to indicate which slots these values should be used to
initialize. You could also define a custom keyword called (for example)
copy-from:
that just takes a source object instead of individual slot
values: let obj4 = make(
<my-class>, copy-from: obj3
);(Example implementations
are at the end of this entry.)4.
By default,
shallow-copy()
is defined only for collections, such as lists, vectors, strings, and arrays. In
order to use it with other classes, you must define a method yourself. There is
no automatic way to copy the slots of a user-defined object. A justification for
this is that copying is not usually as simple as just copying the bits of every
slot, so there is no reasonable default copying implementation. Copying some
slots requires copying the objects they refer to, and yet other slots
shouldn’t be copied at all. The same is actually true of C++, where most
non-trivial classes require user-defined copy constructors or
operator=
member functions to handle copying
correctly.Prompted by this
discussion, I did a survey of all the Dylan code in the Gwydion Dylan CVS
repository in ./fundev, ./libraries, and ./src for definitions and uses of
shallow-copy(): <http://www.gwydiondylan.org/cgi-bin/viewcvs.cgi/>As
expected, there are relatively few occurrences of
shallow-copy().
Also important to note is that only two or three
shallow-copy()
methods are effectively trivial slot copying. Furthermore, it turns out I wrote
one of them (back in 1997) and looking at the code now I realize it may not even
have been necessary or
desirable.So the answer to the
original question is that Dylan assignment does not copy, you must explicitly
copy objects using functions you provide implementations for, and in any case
you’re not going to have to do that very
often.Example
Implementations of
CopyingAlthough I had
originally intended to end this blog entry here, I’ve decided to provide
some details that may help make some of this discussion a little more concrete
for those unfamiliar with
make()
and
shallow-copy().
Given the class
definition: define class
<my-class>
(<object>) slot
foo, init-keyword:
foo:; slot bar,
init-keyword:
bar:; end;we
can now simply call
make()
with keywords
foo: and
bar: to
copy slots from another instance as mentioned
above: make(
<my-class>, foo: obj1.foo, bar: obj1.bar
)This is very explicit, and
simple to implement, but perhaps a bit verbose if we need to do this a lot. We
can shorten this a bit by defining an
initialize()
method that implements a single
copy-from:
keyword that takes an object from which to copy the
slots: define method
initialize (obj :: <my-class>, #key
copy-from) next-method();
// first, perform inherited
initialization if
(copy-from) // if supplied, copy the source object's
slots obj.foo
:=
copy-from.foo; obj.bar
:=
copy-from.bar; end; end
method;Now we have the
additional option of
writing: make(
<my-class>, copy-from: obj1
);Another approach is to
implement a
shallow-copy()
method, like so: define
method shallow-copy (obj ::
<my-class>) make(
<my-class>, foo: obj.foo, bar: obj.bar
) end
method;(Also note we could
both implement
copy-from:
and use it to implement
shallow-copy(),
if desired.)Notice that all
we’ve done is wrap up the verbose call to
make() in
shallow-copy().
The behavior is the same whichever you call. This points out that for some
cases, writing a
shallow-copy()
method may be unnecessary. Implementing
shallow-copy()
helps wrap your copying code up in a well-known copying protocol that can be
used to copy objects without knowing exactly how to copy them, but sometimes
that generality isn’t strictly necessary. It depends on the code in
question and where the abstraction boundaries
are.Finally, notice that
it’s just as easy for you to define your own copying function if
shallow-copy()
isn’t appropriate. For example, if you want to perform copying that is
deeper, but only for certain
slots: define method
copy-in-my-own-special-way (obj ::
<my-class>) make(
<my-class>, foo: shallow-copy( obj.foo ), bar: obj.bar
); end
method;This custom copying
function copies part of the object a little more deeply, by shallow copying the
obj.foo
value. Remember, Dylan never copies implicitly, so
obj.bar
is not copied; instead, the new object’s
bar slot
is bound to the same object as the source object’s
bar. In
contrast, by calling
shallow-copy()
with
obj.foo,
we make a (shallow) copy of its value, so that
foo in
the new object is distinct from
foo in
the original object.
Posted at 12:33 AM
Thu - November 11, 2004
A Round of Applause for OmniGraffle
A plug for OmniGraffle, the greatest
graphics tool since sliced vectors.
Recently, in my work on the online Dylan Reference
Manual, I needed to recreate some class hierarchy
diagrams and other figures that are a part of the printed DRM, but
which were never provided with the HTML
version.They tend to be
structured diagrams with boxes and arrows and descriptive text, the kind of
stuff that is tedious to draw by hand, but which should be a breeze with a
capable piece of software that knows how to arrange and draw these patterned
designs.As it turns out, there is
a wonderful tool for doing these kinds of graphics, which made my life a lot
easier:
OmniGraffle
by The Omni
Group.It occurred to me
that I should give them a plug in my blog for all the help they gave
me:Plug, plug,
plug.So, there you
go.
Posted at 04:42 AM
Tue - November 9, 2004
How to Spot Nerds From Quite a Long Way Away
In which our hero has a revealing moment
of self-observation involving a container of pudding.
I was preparing to eat some chocolate
pudding when I started thinking about how, once opened, it always develops pools
of liquid (water, presumably) on top when left in the fridge, which I find a
little gross, and I always scoop that top part off when I continue eating it
later. I wondered why there was no liquid to start
with.
Today, as I was opening this
fresh container of pudding, I realized that it has a plastic seal that touches
the surface of the pudding, so I guess it keeps the water at bay. Once removed,
the water has room to move to the surface, or perhaps it’s the exposure to
air that draws the water out.
Then
I started thinking, “I guess pudding is a suspension, where the liquid and
the solids aren't entirely ‘fused’ and the water can seep to the
surface,” which then led me to ask (silently, to myself), “is
pudding a liquid, a solid, a suspension, or
what?”
Finally, I went
“meta” and realized that was how I could tell I was a geek: I was
wondering about what kind of state of matter chocolate pudding
is†.
And, of course, writing
“I went ‘meta’” is probably a dead giveaway,
too.
†Bad grammar, no
biscuit! (But I like the way it flows, or, rather, the way it completely fails
to do so.)
Posted at 04:38 PM
Mon - November 8, 2004
Online Dylan Reference Manual Redesign
I've completely redesigned the online
Dylan Reference Manual, making it easier to read and to use as a convenient
reference for the Dylan programming language.
It's been a while since my last
Dylan-related blog entry, and partly it's because I've been spending most of my
Dylan-related time for the past few weeks on heavily editing the online Dylan Reference
Manual. My desire is to make sure the online DRM is an excellent
reference for the Dylan language, and I think this update has made a lot of
improvements (unfortunately, there are some real problems with the quality of
the machine-generated HTML that make it less readable, but I've fixed a lot of
them and I hope to address them all
eventually).If you're curious
about the Dylan language, or you've taken a look at the online DRM before and
found it lacking as a tool for learning about the language, please take a look
at the new version.Here's a copy
of my commit message with the details of the
changes:Complete redesign of the
online DRM, including a new navigation bar on the left, which features
convenient access to interior links to items on each page (this was previously
relegated to the bottom of the page, and is particularly useful for navigating
longer, more complex pages).I
also used CSS to make the printed rendition of the HTML more reasonable. All the
navigation items are hidden, and links are printed in plain text, without
underlines or blue coloring.While
I put CSS to good use, I also tried to make the HTML produce a more reasonable,
usable rendition than before, when CSS isn't supported or stylesheets are
disabled.Overall, the layout and
typography is now much closer to the printed DRM, and I've reconstructed some
content that was apparently lost during the automatic translation to
HTML.I made use of CSS to hide
some information not in the printed DRM or that was just cluttering things up,
while leaving it in the HTML in case we want to make use of it in the future.
(e.g., in the navigation links, it includes tags like "[Open Generic Function]",
which are no longer displayed.)I
consolidated all the HTML pages of Appendix B, Exported Names, into one page.
There was no reason for it to be split up and it just made it harder to
navigate.I added "disabled"
renditions of each navigation button image, so the set of buttons doesn't change
on pages where one or more buttons don't
apply.Too many fixes and
adjustments to tagging to mention, but this includes fixing the tagging of all
the function signatures (though not all the G.F. method
signatures).In fact, this work
began as merely an attempt to fix the signature tagging, but once you start
making global changes to over a hundred HTML files in dire need of cleanup, it's
hard to find a good stopping point. Or maybe that's just me.
Posted at 11:47 PM
|
Calendar
| | Sun | Mon | Tue | Wed | Thu | Fri | Sat
|
Categories
Archives
XML/RSS Feed
Blog Roll
Statistics
Total entries in this blog:
Published On: Aug 12, 2007 06:47 PM
|