Is Go suitable for building DSL?

5,749 views
Skip to first unread message

Andrea Fazzi

unread,
Mar 12, 2010, 3:43:23 PM3/12/10
to golang-nuts
Hi all,

I wrote a blog post trying to answer to the question on subject. Feedbacks
are greatly appreciated!

http://freecella.blogspot.com/2010/03/is-go-suitable-for-building-dsl.html

Thank you,
Andrea

--
Andrea Fazzi @ alcacoop.it
Read my blog at http://freecella.blogspot.com
Follow me on http://twitter.com/remogatto

cthom lists

unread,
Mar 12, 2010, 4:39:25 PM3/12/10
to golang-nuts
I don't understand how anything in that article pertains to the creation of a Domain-Specific Language.

It seems more like a comparison of ruby vs go letter counts.

Am I confused about what a DSL is?

Eleanor McHugh

unread,
Mar 12, 2010, 5:30:18 PM3/12/10
to golang-nuts
On 12 Mar 2010, at 21:39, cthom lists wrote:
> I don't understand how anything in that article pertains to the creation of a Domain-Specific Language.
>
> It seems more like a comparison of ruby vs go letter counts.
>
> Am I confused about what a DSL is?

One of the things Ruby does particularly elegantly is get out of the way when building DSLs and consequently it's a coding style which is prevalent. Whilst it's true that letter count alone isn't a dominant consideration for such things, verbosity of expression does play a significant role in deciding whether similar techniques are idiomatic in any given language and so that's what we really need to be considering.

Based on my experiments to date (and this is by no means a scientific opinion, so take with a large pinch of salt) I'd say that Go is appreciably faster to code in than comparable statically compiled languages, but the verbosity that brings makes internal DSLs less appealing than in Ruby. I've commented before on the lack of method_missing and the fact that explicitly checking for a method's presence is more cumbersome, but setting that aside I think the main reason that Ruby-style DSLs would be tedious to implement in Go is the very different approach taken to parsing the language in the first place.

Go's parser aims to be clean, fast and simple. That means that a certain amount of work has to be undertaken by the programmer. Ruby on the other hand has possibly the most complex and unwieldy parser it's ever been my misfortune to study. However parsing the language is a pig because its syntax is focused on ease of expression for the programmer with no real concessions to computational efficiency or parser clarity.

Whilst I have no desire for Go to become a low-level clone of Ruby I would like to see a little more of its underlying philosophy reflected in the language specification: make programmer happiness a primary goal and try to replicate as many of the elegant idioms of the dynamic world as make sense in a mostly static language.

In terms of practical implementation, the real edge that Ruby has in this regard is that like Lisp it's expression-oriented. Every Ruby statement returns a value and this makes it very easy to be succinct - especially when combined with exception handling for out-of-band. The same thing can be achieved in a more traditional language: Icon famously combined an Algol-derived syntax with out-of-band success/failure and generators to achieve much the same ease of development in a compiled language that many dynamic language enthusiasts have come to expect in Ruby or Python.

It's not entirely unreasonable to foresee Go developing along these lines in the future, but it would at some stage become a markedly different language to the one we're working with today.


Ellie

Eleanor McHugh
Games With Brains
http://feyeleanor.tel
----
raise ArgumentError unless @reality.responds_to? :reason

David Roundy

unread,
Mar 12, 2010, 5:47:36 PM3/12/10
to Eleanor McHugh, golang-nuts
On Fri, Mar 12, 2010 at 2:30 PM, Eleanor McHugh
<ele...@games-with-brains.com> wrote:
> Go's parser aims to be clean, fast and simple. That means that a certain amount of work has to be undertaken by the programmer. Ruby on the other hand has possibly the most complex and unwieldy parser it's ever been my misfortune to study. However parsing the language is a pig because its syntax is focused on ease of expression for the programmer with no real concessions to computational efficiency or parser clarity.

I agree, which is why I'd say making a DSL within go is a bit silly.
Haskell (also statically-typed) is a great languages for embedded
DSLs, because of its insane degree of syntactic flexibility. e.g. you
can define whatever operators you want, with your choice of
precedence. Without that kind of syntactic flexibility (or something
similar, as in ruby), what you have is a library, not a DSL.

However, this comes at a cost, and that cost isn't just in parser
complexity: it's also in human complexity. Just as the compiler
cannot parse Haskell code without compiling any code that it imports,
neither can a human! So what on the surface seems very, very
programmer-friendly, can easily become very, very programmer-hostile
(and this does happen in practice... I've done it myself!). Being
able to read and parse a file without reading and parsing a whole lot
more files is a *very* nice feature, and I'd be sad to see go lose it!
(True, to know what the functions and methods actually do, you need
to look elsewhere, but at least you know that they are functions and
methods, respectively... and you know which are being called with
which arguments.)

Having used and written a number of DSLs, I'd prefer a regular library
any day of the week. With the one exception (everyone has to have
one, right?), which is that I'd love to be able to overload arithmetic
operators for vector and matrix operations and the like. Which is
effectively a DSL for math and Physics.

David

Eleanor McHugh

unread,
Mar 12, 2010, 7:15:13 PM3/12/10
to golang-nuts
On 12 Mar 2010, at 22:47, David Roundy wrote:
> On Fri, Mar 12, 2010 at 2:30 PM, Eleanor McHugh
> <ele...@games-with-brains.com> wrote:
>> Go's parser aims to be clean, fast and simple. That means that a certain amount of work has to be undertaken by the programmer. Ruby on the other hand has possibly the most complex and unwieldy parser it's ever been my misfortune to study. However parsing the language is a pig because its syntax is focused on ease of expression for the programmer with no real concessions to computational efficiency or parser clarity.
>
> I agree, which is why I'd say making a DSL within go is a bit silly.
> Haskell (also statically-typed) is a great languages for embedded
> DSLs, because of its insane degree of syntactic flexibility. e.g. you
> can define whatever operators you want, with your choice of
> precedence. Without that kind of syntactic flexibility (or something
> similar, as in ruby), what you have is a library, not a DSL.

Very true. Every time I think I've explored the furthest reaches of Ruby's syntactic flexibility, I find yet another bit of code that does something utterly unexpected - and utterly delightful :)

> However, this comes at a cost, and that cost isn't just in parser
> complexity: it's also in human complexity. Just as the compiler
> cannot parse Haskell code without compiling any code that it imports,
> neither can a human! So what on the surface seems very, very
> programmer-friendly, can easily become very, very programmer-hostile
> (and this does happen in practice... I've done it myself!). Being
> able to read and parse a file without reading and parsing a whole lot
> more files is a *very* nice feature, and I'd be sad to see go lose it!
> (True, to know what the functions and methods actually do, you need
> to look elsewhere, but at least you know that they are functions and
> methods, respectively... and you know which are being called with
> which arguments.)

I do love the compilation speed compared to C++ etc., but I don't think that aspect of Go necessarily has to change to be more friendly to DSL design. Ruby gets a ridiculous amount of mileage out of first-class symbols and three very simple syntax rules:

1. Allowing the last argument to a method to be a hash of values

some_method(:a => 13, :z => 'dog')

is similar to Go's variadic parameter lists if they were defined as

map[symbol]interface{}

2. Allowing each method to accept an implicit block (anonymous closure)

def some_method
yield 3, 5
end

some_method do |x, y|
x + y ** 2
end

which is equivalent to

func some_method(block func(x, y int) int {}) int {
block(3, 5)
}
some_method(func(x, y int) int { return x + y ** 2 })

3. braces are optional for function calls except where this would be ambiguous

> Having used and written a number of DSLs, I'd prefer a regular library
> any day of the week. With the one exception (everyone has to have
> one, right?), which is that I'd love to be able to overload arithmetic
> operators for vector and matrix operations and the like. Which is
> effectively a DSL for math and Physics.

I suspect there's an analogue to Greenspun's tenth rule involving ad hoc implementations of Fortran :)

Esko Luontola

unread,
Mar 12, 2010, 7:47:42 PM3/12/10
to golang-nuts
I've implemented one internal DSL in Go when creating GoSpec. With the
help of first class functions, varargs and closures I was able to
create a reasonably succinct and expressive DSL [1], but it's still
far from what's possible in languages like Scala [2] which also is
statically typed. But Scala's compiler is very slow and the language
complex, so Go seems like a reasonable compromise in expressiveness,
simplicity and compiler speed.

[1] http://github.com/orfjackal/gospec/blob/release/examples/stack_test.go
[2] http://code.google.com/p/specs/wiki/QuickStart

Steven

unread,
Mar 13, 2010, 7:05:25 PM3/13/10
to Esko Luontola, golang-nuts


Random thought:

What if there was an operator keyword?

operator (leftOperand[s]) Op (rightOperand[s]) (result) { }

Can of worms, but not having them be functions/methods might save some
problems. (note this isn't operator overloading; it could be, but
that's something else entirely)

In order for it to work, it would either have to be inlineable, or
work with references (no extra syntax) or both. Operand lists > 1
would have to be put in parenthases (maybe) to keep parsing simple.

I'm not entirely sure how much it would benifit in general though.

Otherwise, Go just isn't designed to let you elide syntax wherever
possible, it's designed to be regular, which means it may not be
suited for making up your own internal language if you don't like
being tied down by the syntax.

Andrea Fazzi

unread,
Mar 14, 2010, 10:45:09 AM3/14/10
to golang-nuts
Excerpts from Andrea Fazzi's message of ven mar 12 21:43:23 +0100 2010:

> Hi all,
>
> I wrote a blog post trying to answer to the question on subject. Feedbacks
> are greatly appreciated!
>

Thank you all for your feedbacks. I want to cite an interesting (though
anonymous) comment on my blog:

> What about this approach (look at main):
>
> http://pastie.org/pastes/867451
>
> Really, Go has pretty decent literal syntax for most things, so just
> using them w/ some helper functions is the right way for pretty much
> everything, in my experience.

She/he proposed an interesting DSL pattern: a sort of type aliasing
supported by helper functions.

What do you think about this approach?

p.s.

If possible, I'd like to know her/his name in order to give credits.

Thank you,

Steven

unread,
Mar 14, 2010, 3:34:15 PM3/14/10
to Andrea Fazzi, golang-nuts
Just to point out: wouldn't this be better logic that helps prevent misuse? There's so many ways that can silently not work...


Note: The specific error messages are not the point, but rather that they are there.

Steven

unread,
Mar 14, 2010, 4:01:08 PM3/14/10
to Andrea Fazzi, golang-nuts
By the way, I wish func types could be used in function literals, then this could change to:

bye := task(
Action { println("Bye!") },
DependsOn {hello, world})

I can understand there being parsing ambiguity between a composite literal and a func literal, but it would be logical from a human perspective, and it would be convenient. But the extra func() isn't all that bad.

Andrea Fazzi

unread,
Mar 14, 2010, 5:02:48 PM3/14/10
to golang-nuts
Excerpts from Steven's message of dom mar 14 20:34:15 +0100 2010:

> Just to point out: wouldn't this be better logic that helps prevent misuse?
> There's so many ways that can silently not work...
>
> http://pastie.org/869326
>
> Note: The specific error messages are not the point, but rather that they
> are there.

Hi Steven,

you make a valid point. Indeed, when using varargs it's often necessary to
handle exceptions regarding an incorrect number of arguments. However, I
think that the actual intentions of the OP was merely didascalic without
the claim to give a complete implementation.

Cheers,

Andrea Fazzi

unread,
Mar 14, 2010, 5:11:08 PM3/14/10
to golang-nuts
Excerpts from Steven's message of dom mar 14 21:01:08 +0100 2010:

> By the way, I wish func types could be used in function literals, then this
> could change to:
>
> bye := task(
> Action { println("Bye!") },
> DependsOn {hello, world})
>
> I can understand there being parsing ambiguity between a composite literal
> and a func literal, but it would be logical from a human perspective, and it
> would be convenient. But the extra func() isn't all that bad.

On second thought, I wonder if a pure method chaining approach could lead
to an even more readable DSL:

Task("A").DependsOn("B", "C").Do(func() {
// do something
}

The current implementation is:

Task("A", func() {
// do something
}).DependsOn("B", "C")

How does it look?

Cheers,

Steven

unread,
Mar 14, 2010, 5:27:11 PM3/14/10
to Andrea Fazzi, golang-nuts
On Sun, Mar 14, 2010 at 5:02 PM, Andrea Fazzi <andrea...@alcacoop.it> wrote:
Hi Steven,

you make a valid point. Indeed, when using varargs it's often necessary to
handle exceptions regarding an incorrect number of arguments. However, I
think that the actual intentions of the OP was merely didascalic without
the claim to give a complete implementation.

Sure, I was just pointing out that the type switch might not be the way to go. In fact, an even better one might be:

Which, since its compile time checked (mostly) it takes less runtime checking. It also requires no type aliasing, yet leaves the usage exactly the same (save for the no-longer-needed type cast).


On Sun, Mar 14, 2010 at 5:11 PM, Andrea Fazzi <andrea...@alcacoop.it> wrote:
Excerpts from Steven's message of dom mar 14 21:01:08 +0100 2010:

On second thought, I wonder if a pure method chaining approach could lead
to an even more readable DSL:

Task("A").DependsOn("B", "C").Do(func() {
   // do something
}

The current implementation is:

Task("A", func() {
   // do something
}).DependsOn("B", "C")

How does it look?

Better and less choppy. As an implementation note, having Task and DependsOn return a different type than an actual task type could help prevent accidental misuse, and would allow more control on mutability of an existing task (the final task type need not have a DependsOn or a Do method). Then the only way to get a true Task is to call the Do method, so you couldn't get an incomplete one by accident.

Eleanor McHugh

unread,
Mar 14, 2010, 7:07:15 PM3/14/10
to golang-nuts
On 14 Mar 2010, at 21:11, Andrea Fazzi wrote:
> On second thought, I wonder if a pure method chaining approach could lead
> to an even more readable DSL:
>
> Task("A").DependsOn("B", "C").Do(func() {
> // do something
> }
>
> The current implementation is:
>
> Task("A", func() {
> // do something
> }).DependsOn("B", "C")
>
> How does it look?

From the perspective of readability method chaining definitely has advantages, but with Go being both a statement-oriented language and supporting multiple return values there's a natural pressure not to use it extensively.

Steven

unread,
Mar 15, 2010, 1:18:33 AM3/15/10
to Eleanor McHugh, golang-nuts
The existence of multiple return doesn't preclude chaining. If multiple return doesn't make sense for what you're doing, then there's no need to change you're design to account for it. And if the whole point is to make your own DSL, then taking into consideration Go programming norms also doesn't make sense.

(note, this is also where operators would come in handy, since they could conveniently receive multiple left operands)

Question for OP, what is the advantage of using strings for identifiers in the context of Go?

Eleanor McHugh

unread,
Mar 15, 2010, 9:28:14 AM3/15/10
to golang-nuts
On 15 Mar 2010, at 05:18, Steven wrote:
> On Sun, Mar 14, 2010 at 7:07 PM, Eleanor McHugh <ele...@games-with-brains.com> wrote:
>> From the perspective of readability method chaining definitely has advantages, but with Go
>> being both a statement-oriented language and supporting multiple return values there's a
>> natural pressure not to use it extensively.
>
> The existence of multiple return doesn't preclude chaining. If multiple return doesn't make sense for what you're doing, then there's no need to change you're design to account for it. And if the whole point is to make your own DSL, then taking into consideration Go programming norms also doesn't make sense.

Rule #1 of writing code: it should play nicely with others.

chris dollin

unread,
Mar 15, 2010, 9:51:26 AM3/15/10
to Eleanor McHugh, golang-nuts
On 15 March 2010 13:28, Eleanor McHugh <ele...@games-with-brains.com> wrote:
Rule #1 of writing code: it should play nicely with others.

 
Rule 0 of writing code: every language has its own grain.

--
Chris "allusive" Dollin

Andrea Fazzi

unread,
Mar 15, 2010, 3:06:51 PM3/15/10
to golang-nuts
Excerpts from Steven's message of lun mar 15 06:18:33 +0100 2010:

> Question for OP, what is the advantage of using strings for identifiers in
> the context of Go?

I assume you're referring to the string arguments passed to DependsOn().

Well, the advantage to use strings rather than task values is that I can
define tasks without worry about order of definition. E.g.

Task("A", func () {}).DependsOn("B")
Task("B", func () {})

The Task() function doesn't run tasks immediately. It's used to set up
tasks attributes and dependencies among tasks. The responsability to
actually invoke tasks in the right order is deferred to a taskmanager
object. If you look at the code of the anonymous poster you'll be aware
that - in her/his implementation - the order of definition does matter.

Hope I answered to your question.

Reply all
Reply to author
Forward
0 new messages