Looks like it is a good thing that I picked that Erlang book, Boo just got Pattern Matching, I am looking at the code, and but for the grace of Erlang, I would be completely lost.
I am toying with this DSL:
recieve: message msg as ChangeAddressMessage if msg.AddressId is null: transaction: address = Address.FromMessage(msg) address.Create() message msg as ChangeAddressMessage: transaction: address = LoadAddress(msg.AddressId) Address.FromMessage(address, msg) address.Save() message other: raise MessageNotUnderstood()
What book am I reading now?
Michael Dorfman presents an interesting question about building internal DSLs.
[You should] point out for those of us who have not (yet) tasted the Boo koolaid exactly what makes this language more suitable to DSLs than VB9, C#3, F#, IronPython or IronRuby.
It's not enough to say "You can't build DSLs in C# / VB, they aren't flexible enough (at all)", you need to mention which language features are missing.
So, why am did I say that C#/VB/Java/etc are not flexible enough to build usable DSLs out of?
Let us consider what we want to achieve when we are thinking about a DSL. The first thing that comes to mind is that is should be about the domain, so I want to have as few programming related stuff as possible, and the syntax should be simple and clear to read.
Statically typed languages are historically rigid in their structure, and force you to follow specific syntax rules. Dynamic languages are far more relaxed in that manner. Boo is a statically typed language that masquerade as a dynamic language, so it sits with the dynamic languages on this matter.
I am not familiar enough with F# to give an answer on that, but both ruby & python can certainly build DSLs. DSLs are very common in ruby, and I think that python can do much the same, although I do wonder about the white space sensitivity thing regarding DSLs.
But let us explore for a moment the idea of build a DSL in a statically typed language. We want to define a DSL for executing tasks in a build process. We want to execute a process and copy some files. C# (for java, just change the using to imports):
using My.Imaginary.Build.Framework; using My.Imaginary.Build.Framework.IO; public class MyCustomTask : TaskBase { public MyCustomerTask() : TaskBase("Execute process & copy some files") { } public override void Execute() { Execute("msbuild.exe", "myProject.sln"); CopyFiles("bin\debug\**.*", "ftp://staging-srv/myApp/"); } }
And in VB.Net:
Imports My.Imaginary.Build.Framework Imports My.Imaginary.Build.Framework.IO Public Class MyCustomTask Inherits TaskBase Public Sub New() MyBase.New("Execute process & copy some files") End Function Public Overrides Sub Execute() Execute("msbuild.exe", "myProject.sln") CopyFiles("bin\debug\**.*", "ftp://staging-srv/myApp/") End Sub End Class
There is a lot of syntactic noise here that we need just to make the compiler happy. We aren't working with a DSL, at best, we are working with a fluent interface (more on that later).
We could probably do this, though:
Description("Execute process & copy some files"); Execute("msbuild.exe", "myProject.sln"); CopyFiles("bin\debug\**.*", "ftp://staging-srv/myApp/");
And then pre-process the file in order to put all the rest of the noise around it, but that is going to be bite us if we need to do things such extending it further (how do we add a using statement here? how do we define a method?), or have anything even slightly more complex than a series of sequential operations?
Sure, I can think of solutions for all of the above, but they #include stuff that is (a) hard, (b) unpleasant, (c) complex and (d) leaky.
When you are building DSLs, you want as few constraints as possible from the language, and that is not possible to do in static languages. But, I can hear you say, we have Linq and extension methods and fluent interfaces now, surely that is a solved problem.
Not really, I believe that I have pushed C# 2.0 as far as it is possible to go in regards to fluent interfaces, and I have looked very closely at what I can do with C# 3.0 new syntax. The answer is that it is great for programmers, but not really useful for developing DSLs. You are still going to have to deal with making the compiler happy, and your ability to modify the syntax of the language itself to fit the ideas that you want to express is nil.
In comparison, here is how you can express the same idea in Boo:
Task "Exexcute process & copy some files: Execute "msbuild.exe", "myProject.sln" CopyFiles "bin\debug\*.*", "ftp://staging-srv/myApp"
There is nothing here that is here to make the compiler happy, we are focused on what we want to do, not on the syntax.
Beyond that, we have the Boo compiler itself, which allows you to go far beyond that. This goes a bit beyond the scope of DSL, but this is valid Boo code:
receive: message msg as ChangeAddressMessage if msg.AddressId is null: transaction: address = Address.FromMessage(msg) address.Create() message msg as ChangeAddressMessage: transaction: address = LoadAddress(msg.AddressId) Address.FromMessage(address, msg) address.Save() message other: raise MessageNotUnderstood()
Boo doesn't have the receive / message primitives, nor a transaction keyword, but it is trivial to add them (well, trivial to add the keywords, the messaging infrastructure is not trivial).
This expose an interesting concept that I have yet to talk about, Boo allows me to do code transformation at compile time. This means that even if the way that we want to express something is not the way we would like it to execute, we can have the best of both worlds, we can express it clearly, and translate it to the way it should be executed.
That is not something that you can really do in most other languages, certainly not in statically typed ones. Yeah, I know about assembly weaving, that is now what I call a DSL. I think that Ruby can do something similar, using ParseTree and eval(str) {don't think Ruby has a way to translate expression list to code, but again}, but I am not sure.
So, anyway, those are my reasoning for using Boo.
I know that I am asking the question from a self-selecting group, but what the hell.
I am considering the idea of writing a book about writing domain specific languages, with Boo as the implementation language. This has two distinct topics:
- Theoretical knowledge:
- DSL Usages
- How to create the syntax
- How to integrate into an application
- Testing
- Versioning
- Patterns
- Implementation strategy - how to actually build a useful DSL that can do all of the above using Boo.
I certainly like the subject, but I wanted to know what you think about it.
Do you find the idea useful? Is there enough interest in it? Do you (or people you know) use DSLs for work/fun? Any other comment?
Okay, since I have started thinking about using:
import file from anotherFile.boo
It occurred to me that there are plenty of reasons to want to do similar things. And boo is flexible enough to make some interesting things with it.
What about bringing in WSDL definition at compile time?
import webservice from "http://localhost/who/is.there" as WhoIsThereService
And then writing a small compiler step that does all the rest.
Usually the first twenty or so lines of my my Windsor.boo file are just import statements, they really make it harder to read, why not split them to another file?
import all from imports.boo
I can probably think of a few other interesting things to do with it, as well.
While a DSL can really cut down the amount of code that you need to write, putting everything in one file is not a good idea. But I like my DSLs to be script like, without the need for an explicit compilation step. I have thought about it for a while now, and yesterday ago Craig Neuwirt* asked how it can be done using Binsor.
My default language is Boo, obviously, which is a static, compiled language that pretends that it is dynamic and scriptish, giving the best of all worlds. One of the cool things about Boo is that you can reference an assembly by just doing this:
import System.Data.SqlClient from System.Data
This make it very easy to write scripts, because you just declare things and they will be automatically referenced, without needing to pass parameters to the script runner.
Now, I wanted to have some way to handle file inclusion as well. But Boo doesn't support this. This is not something that you can do in a method, or dynamic loading, you have to do this as part of the compilation process.
In a normal language, I would be left with building pre-processors, or doing heuristics to find all the relevant files that I want, or write an XML file that would detail which file I want to include. But Boo is not a normal language, it is a Super Language. It let me do what I want. And I wanted to have file references.
So I did.
I love Boo, I really do. The initial sample I started with was this. This is actually on the examples, what do you know. Then I thought about how I wanted to it, and I figured out that I can do this:
import file from anotherFile.boo
Boo allows me to modify the compiler object model (AST) at compilation, so I just detect all the imports for the namespace "file", and then I compile and reference that file. I can even do this:
import file from "~/config/anotherFile.boo"
Where ~ is translated to the AppDomain BaseDirectory.
You can find the code here, about 100 lines of code, out of which around 30 could be YAGNIed away. Wasn't very hard, and make me love Boo all over again, for what it allows me to do.
* Who doesn't blog, and a search for "Craig Neuwirt blog" brings back my blog :-) (hint)
I had time today to sit on several Brail bugs. I keep getting more and more and more amazed by the power that Boo is giving me.
In order to fix some of those bugs, I had to literally change the meaning of the if statement. (I defined my own nullable propagator, which I had to implement deeply into the language).
Damn, I love this language.
Oh, and FYI, if 2+2 == 4 will not do the expected thing anymore :-)
Here is the first such script that I am going to use:
import Boobs.IO.Extensions
import Boobs.Compiler.Extensions
Task "default", ["build"]
Task "build", ["clean"]:
MsBuild("MyApp.sln").Execute()
Task "clean":
MsBuild("MyApp.sln", "/target:clean").Execute()
Svn "ContiniousIntegration":
SvnUpdate
Execute("default")
Note that this is probably incredibly primitive consider what rake can do, but it amuses me to be able to do this.
The commands are probably even more fun:
Tools\BooBuildSystem\boobs -f:default.boobs -t:ContiniousIntegration
The Svn task will only be run if the repository is more recent than the working copy, so this is something that makes it very easy to build CI scripts. In this case I need to do something a distributed deployment on commit, and while I can probably do this using CC.Net, I am not really fond of the anticipated pain (configuring permissions alone would be a PITA, I imagine) and the executable XML.
This is far simpler, if more primitive.
As an aside, I am getting addicted to parenthesis-less method calls.
The slides & code from my JAOO talk can be found here.
I hate XML, a long time ago, I also hated XML, but I also had some free time, and I played with building a build system in Boo. To match NAnt, I called it NUncle.
It never really gotten anywhere, but Georges Benatti has taken the code and created the Boo Build System. I am just taking a look, and it is fairly impressive. It has the concept of tasks and dependencies between them, as well as action that it can perform.
Here is a part of Boobs' own build script:
Task "build boobs", ["build engine", "build extensions"]: bc = Booc( SourcesSet : FileSet("tools/boobs/**/*.boo"), OutputFile : "build/boobs.exe" ) bc.ReferencesSet.Include("build/boobs.engine.dll") bc.ReferencesSet.Include("build/boo.lang.useful.dll") bc.Execute() Task "build engine": Booc( SourcesSet : FileSet("src/boobs.engine/**/*.boo"), OutputFile : "build/boobs.engine.dll", OutputTarget: TargetType.Library ).Execute() Task "build extensions", ["build io.extensions", "build compiler.extensions"] Task "build io.extensions": Booc( SourcesSet : FileSet("src/extensions/boobs.io.extensions/**/*.boo"), OutputFile : "build/boobs.io.extensions.dll", OutputTarget: TargetType.Library ).Execute() Task "build compiler.extensions": bc = Booc( SourcesSet : FileSet("src/extensions/boobs.compiler.extensions/**/*.boo"), OutputFile : "build/boobs.compiler.extensions.dll", OutputTarget : TargetType.Library ) bc.ReferencesSet.Include("build/boobs.io.extensions.dll") bc.Execute()
I don't know about you, but this makes me feel very nice.
The concept is pretty obvious, I feel, and the really nice thing is that extending it is a piece of cake. Here is how you validate dates of two files:
def IsUpToDate(target as string, source as string): return true unless File.Exists(source) return false unless File.Exists(target) targetInfo = FileInfo(target) sourceInfo = FileInfo(source) return targetInfo.LastAccessTimeUtc >= sourceInfo.LastAccessTimeUtc
And its usage:
Cp("source.big", "dest.big") if not IsUpToDate("source.big", "dest.big")
Or, you know what, this is fairly routine, and it comes as part of the standard library for Boobs. Let us create something new, ConditionalCopy:
def ConditionalCp(src as string, dest as string): Cp(src, dest) if not IsUpToDate(src, dest)
Usage should be clear by now, I hope.