Info on Smalltalk DSLs or Metaprogramming...
Rich Warren
rwmlist at gmail.com
Tue Sep 5 06:46:46 UTC 2006
On Sep 4, 2006, at 4:28 PM, Ramon Leon wrote:
>> As an example, Body can take a variable number of Tag objects.
>> Java methods can be defined to accept variable numbers of
>> parameters, so this isn't a problem. Tag objects can also be
>> nested inside other tags (allowing us to express complete html
>> hierarchies). Alternatively, all tags (including body) could
>> accept any objects-- and internally it calls toString() on the
>> object. This would let you pass in a mixture of Tag objects and
>> literal strings.
>
> True, that would seem to work, but only with the addition saying
> all setters return "this", so properties of those objects can be
> set in every possible combination, inline, equivalent to
> Smalltalk's ; operator. I've done this in the past, it's ugly, and
> non idiomatic, and confuses other programmers. It works
> surprisingly well, once accustomed to it, but feels like you're
> working very much against the language. I embedded SQL into csharp
> using this technique, mostly for the experience of doing so.t as
> similar to the API vs framework thing, people tend to like one or
> the other.
Um, it doesn't need to be that difficult. Java basically treats
variable length arguments as a collection. You could simply iterate
over the arguments and add each one (in order) to the tag object.
The original example I gave didn't use any setters--not in the
conventional sense. For example, body() could simply call toString()
on anything passed in, concatenating the string from multiple
arguments. It could then append "<body>" to the front and "</body>"
to the end. Then it would append the whole string to the current
document.
Alternatively, each tag could keep its contents as a list of subtags
and raw string, and everything only get converted to strings (with
the relevant tags added) at the end.
It would still be uglier than Smalltalk, but it could be a very
simple solution. Of course, I'd still prefer to do this in either
Smalltalk, Ruby or Lisp.
>
>> On the other hand, there is a sharp line between interpreted DSLs
>> and regular programming. That's one of the reasons I prefer to
>> use the term DSL strictly for interpreted DSLs. The term (when
>> used that way) has a clear meaning.
>> -Rich-
>
> I fail to see that line, whether code is interpreted or not, imho
> is orthogonal to the issue of whether or not that code is a DSL. I
> find your linking of these concepts, odd, maybe it's me, but they
> simply aren't related.
>
>
OK, clearly you think the issue of interpreted/non-interpreted is
orthogonal to the issue of
DSL/non-DSL. I don't think we're going to agree on this, so let's set
it aside for now. For the following discussion, assume everything is
a DSL unless explicitly stated otherwise.
In other words, let's discuss the difference between bottom-up
embedded DSLs and interpreted embedded DSLs.
For me all Bottom-up DSL and regular, non-DSL code lie on a
continuum. You can clearly point to either end and say "This is a
DLS" or "This isn't", but most code lies somewhere in the middle.
And, the middle gets muddy.
There's a quote I've seen many times (with slightly different
wording). "Any sufficiently complex project becomes a DSL". This is
exactly the type of blurring I'm referring to, and it results in a
general loss of meaning. If everything becomes a DSL, then the term
DSL has no meaning.
On the other hand, interpreted DSL and non-interpreted code is not a
continuum. There is a clear line between the two cases. Something is
either designed to interpret external strings, or it isn't. There's
no ambiguity.
Secondly, interpreted DSLs require different techniques than bottom-
up DSLs. As far as I know, C, C++ and Java cannot natively evaluate
arbitrary strings the way Ruby and Smalltalk can. In those languages,
you cannot even write code to interpret external strings without
building a parser of some sort (thus creating an external DSL). Even
in Smalltalk and Ruby, evaluating strings requires (at minimum) an
additional method call. More importantly, you have to decide what
context you are evaluating the string in.
There are also other decisions that need to be made when building an
interpreting DS that don't have to be made (or cannot be made) when
simply building a bottom-up DSL. For example, how do you handle EOL
symbols? Most Ruby DSLs interpret a line at a time, treating each
line as a separate command. On the other hand, you could interpret
the whole text as a chunk and treat EOLs just like any other white
space.
As you can see, even if you're just repurposing the host language,
building an interpreting DSL requires an additional layer of tricks,
tips and techniques that are not required for bottom-up DSLs.
Finally, there's the underlying reason behind why you're using the
technique. Bottom-up DSLs are simply a tool for developers.
Interpreting DSLS can also simplify the development task for
developers; however, they also give end-users tools to change the
application's behavior after it has been deployed. So, interpreted
DSLs can solve a much broader range of problems than bottom-up DSLs.
All of this (unambiguous meaning, clearly different set of
techniques, and broader range of intentions) are reasons why I prefer
to limit the term DSL to interpreted DSLs. To my mind, there seems to
be obvious ambiguity between what is a bottom-up DSL and what isn't
(as exemplified by the "every significantly complex..." quote).
Bottom-up DSLs don't require any specialized techniques--you're
simply creating useful methods, classes and operators. And Bottom-up
DSLS are simply another type of source code--they don't add any
additional value outside development (here I'm including maintenance
and testing--basically everything that's done in-house).
Bottom line is, my definition would make DSLs more obviously
different from other, more mundane programming techniques.
-Rich-
More information about the Squeak-dev
mailing list
|