F# is a great language to build internal DSLs, but I had no idea that you could go as far as what I’m going to present here…
Let’s start from the start: my goal was to design some way for me to explain my daughter what programming is about and how it works. There are some nice graphical tools for that, like Scratch or more recently Hour of code, however I wanted to show something which is closer to what I actually do on a daily basis: write (and read) code. There are some nice educational programming languages, some of them are even usable in a localized way (French, in my case). LOGO is a good example of this, and I could have used an existing tool, but where is the fun if you don’t build your own?
It appears that building an internal LOGO-like DSL is surprisingly easy, and requires almost no code! What you need is just to define the basic types to describe your actions:
type Distance_Unit = STEPS
type Rotation_Unit = GRADATIONS
type Rotation_Direction = | LEFT | RIGHT
let STEP = STEPS
let GRADATION = GRADATIONS
type Color = | RED | GREEN | BLUE
type Action =
| Walk of int * Distance_Unit
| Turn of int * Rotation_Unit * Rotation_Direction
| LiftPenUp
| PutPenDown
| PickColor of Color
type Turtle = Action seq
And then a computation expression to do the trick of transforming sentences to sequences of actions:
type AS_word = AS
type TO_word = TO
type THE_word = THE
type PEN_word = PEN
type UP_word = UP
type DOWN_word = DOWN
type TIMES_word = TIMES
type WHAT_word = WHAT
type DOES_word = DOES
type TurtleBuilder() =
member x.Yield(()) = Seq.empty
[<CustomOperation("WALK", MaintainsVariableSpace = true)>]
member x.Walk(source:Turtle, nb, unit:Distance_Unit) =
Seq.append source [Walk(nb, unit)]
[<CustomOperation("TURN", MaintainsVariableSpace = true)>]
member x.Turn(source:Turtle, nb, unit:Rotation_Unit, to_word:TO_word,
the_word:THE_word, direction:Rotation_Direction) =
Seq.append source [Turn(nb, unit, direction)]
[<CustomOperation("LIFT", MaintainsVariableSpace = true)>]
member x.LiftPenUp(source:Turtle, the_word:THE_word, pen_word:PEN_word,
up_word:UP_word) =
Seq.append source [LiftPenUp]
[<CustomOperation("PUT", MaintainsVariableSpace = true)>]
member x.PutPenDown(source:Turtle, the_word:THE_word, pen_word:PEN_word,
down_word:DOWN_word) =
Seq.append source [PutPenDown]
[<CustomOperation("PICK", MaintainsVariableSpace = true)>]
member x.PickColor(source:Turtle, the_word:THE_word, color:Color, pen_word:PEN_word) =
Seq.append source [PickColor color]
[<CustomOperation("DO", MaintainsVariableSpace = true)>]
member x.Do(source:Turtle, as_word:AS_word, turtle:Turtle) =
Seq.append source turtle
[<CustomOperation("REPEAT", MaintainsVariableSpace = true)>]
member x.Repeat(source:Turtle, nb:int, times_word:TIMES_word, what_word:WHAT_word,
turtle:Turtle, does_word:DOES_word) =
Seq.append source (List.replicate nb turtle |> Seq.collect id)
let turtle = new TurtleBuilder()
And with nothing more, you can now write this kind of plain English instructions:
turtle {
LIFT THE PEN UP
WALK 4 STEPS
TURN 3 GRADATIONS TO THE RIGHT
PICK THE GREEN PEN
PUT THE PEN DOWN
WALK 4 STEPS }
Now, this doesn’t solve my initial problem, I want a French DSL. But I just need to define another builder, and a few translation functions:
type Tortue = Turtle
type Unite_De_Distance = PAS with
member x.enAnglais = match x with | PAS -> STEP
type Unite_De_Rotation = | CRANS with
member x.enAnglais = match x with | CRANS -> GRADATION
let CRAN = CRANS
type Sens_De_Rotation = | GAUCHE | DROITE with
member x.enAnglais = match x with
| GAUCHE -> LEFT
| DROITE -> RIGHT
type Couleur = | ROUGE | VERT | BLEU with
member x.enAnglais = match x with
| ROUGE -> RED
| VERT -> GREEN
| BLEU -> BLUE
type Mot_A = A
type Mot_DE = DE
type Mot_LE = LE
type Mot_STYLO = STYLO
type Mot_COMME = COMME
type Mot_FOIS = FOIS
type Mot_CE = CE
type Mot_QUE = QUE
type Mot_FAIT = FAIT
type TortueBuilder() =
member x.Yield(()) = Seq.empty
member x.For(_) = Seq.empty
[<CustomOperation("AVANCE", MaintainsVariableSpace = true)>]
member x.Avance(source:Tortue, de:Mot_DE, nb, unite:Unite_De_Distance) =
Seq.append source [Walk(nb, unite.enAnglais)]
[<CustomOperation("TOURNE", MaintainsVariableSpace = true)>]
member x.Tourne(source:Tortue, de:Mot_DE, nb, unite:Unite_De_Rotation,
a:Mot_A, sens:Sens_De_Rotation) =
Seq.append source [Turn(nb, unite.enAnglais, sens.enAnglais)]
[<CustomOperation("LEVE", MaintainsVariableSpace = true)>]
member x.Leve(source:Tortue, le:Mot_LE, stylo:Mot_STYLO) =
Seq.append source [LiftPenUp]
[<CustomOperation("POSE", MaintainsVariableSpace = true)>]
member x.Pose(source:Tortue, le:Mot_LE, stylo:Mot_STYLO) =
Seq.append source [PutPenDown]
[<CustomOperation("PRENDS", MaintainsVariableSpace = true)>]
member x.Prends(source:Tortue, le:Mot_LE, stylo:Mot_STYLO, couleur:Couleur) =
Seq.append source [PickColor couleur.enAnglais]
[<CustomOperation("FAIS", MaintainsVariableSpace = true)>]
member x.Fais(source:Tortue, comme:Mot_COMME, tortue:Tortue) =
Seq.append source tortue
[<CustomOperation("REPETE", MaintainsVariableSpace = true)>]
member x.Repete(source:Tortue, nb:int, fois:Mot_FOIS, ce:Mot_CE, que:Mot_QUE,
tortue:Tortue, fait:Mot_FAIT) =
Seq.append source (List.replicate nb tortue |> Seq.collect id)
let tortue = new TortueBuilder()
And I can write my instructions in French!
tortue {
AVANCE DE 5 PAS
TOURNE DE 6 CRANS A DROITE
AVANCE DE 5 PAS
TOURNE DE 6 CRANS A DROITE
AVANCE DE 5 PAS
TOURNE DE 6 CRANS A DROITE
AVANCE DE 10 PAS }
The next steps involved:
- Writing a Windows Forms client to actually see the turtle draw things
- Making it possible to send actions to the turtle using FSI
- Hand-coding every letter of the alphabet
- Adding a new keyword to my turtle builders
And please welcome my “Hello world” sample:
turtle {
WRITE "MR T. SAYS:\nHELLO WORLD!\n\n"
}
As usual, all the code is available on my github.

