AnnouncementsMatrixEventsFunnyVideosMusicBooksProjectsAncapsTechEconomicsPrivacyGIFSCringeAnarchyFilmPicsThemesIdeas4MatrixAskMatrixHelpTop Subs
2

If you learn something, teach it to others. Then you learn it better. It also has the advantage that if you are someone who likes to learn things, which can be rare, then you help avoid the ever widening gap between you and others, which sounds like it would be an advantage but really just becomes annoying later.

So what is a DSL (domain specific language) and why would you want one? Well, almost all programming languages we touch in one way or another could be considered somewhere on the spectrum of being a domain specific language. But less extremely so. They were all designed to make some problem easier to solve than the languages that existed before them. And isn't it great that we have these nice languages vs coding everything in binary. But what if you want an taylored language for your very niche use case and a full blown programming language just ends up being more verbose for your narrow needs than is ideal? If only making such a language was easy, or at least managable.

Well, Ruby made that relatively managable. The key thing is you can attach a couple more lines to a class and you are there. At least this is true for the most basic case. Almost any language is a tool used to manage the state of either a real system or a virtual system. In the case of Assembly we are managing the state of registers in a CPU (that's pretty real). But we can also manage state in an class. We can define the shape of that state with our instance variables, and we can update that state with methods. Now we just need to make the syntax of doing that a little lighter and that's a DSL.

Wow, simple. It's really not as fancy as the Ruby folks want to pretend it is. But it is convinient.

Let's show some code.

class Mine
 def initialize
  $depth = 0 
  $searchdepth=0
  $gold = 2 
 end 
 def dig 
  raise "Too poor" if dig<1
  $depth+=1
  $gold-=1
 end
 def digmany levels
  raise "Too poor" if dig<levels
  $depth+=levels
  $gold-=levels
 end
 def search
  $gold+=(3.0*($depth-$searchdepth)*rand).round
  $searchdepth=$depth
 end 
 def print
  puts "depth(#$depth) searchdepth(#$searchdepth) gold(#$gold)"
 end 
 def self.script(&block)
  mine = new 
  mine.instance_eval(&block)
  mine.print
  mine
 end 
end

That is a DSL for scripting the action of digging a hole and searching for gold in that hole where you havn't yet looked. Let's add a usage example.

Mine.script do
 dig
 print
 search
 print
 digmany 2
 print
 search
end

Notice that I didn't end with a print. That was because the way I wrote the script function it will print as it exits. This is a pretty normal pattern for a DSL. Make the object, execute the block with the object coupled as the context. Perform any side effects at the end based on the end state of the object. Other languages have those abilities. But what does more heavy lifting that any truly unique language features (at least by today's standards) is the aestetic that ruby gives us where a naked function is a call of that function.

In Javascript a naked use of a function's name is just a reference to that function. That does make it easy to pass that function around as a value without extra syntax. But to call a function you have to use parenthisis (in JS). It's such a small detail you would think it shouldn't give Ruby an advantage for making DSLs. But what makes a DSL special is removing all unneeded text and formatting overhead. And once that starts getting really small, every bit more you remove become proportionally significant.

A more practical example would be a language that actually does something like KubeDSL

KubeDSL.service do
  metadata do
    name 'my-service'
    namespace 'my-namespace'
    labels do
      add :app, 'my-app'
      add :role, 'web'
    end
  end
  spec do
    type 'NodePort'
    selector do
      add :app, 'my-app'
      add :role, 'web'
    end
    port do
      name 'http'
      port 3000
      protocol 'TCP'
      target_port 'http'
    end
  end
end

Just for fun I thought I would try using the same pattern in Javascript using its original form of classes.

function Mine() {
 this.depth=0;
 this.searchdepth=0;
 this.gold=2;
}
Mine.prototype.dig=function () {
 if(this.gold<1) throw new Error("Too poor");
 ++this.depth;
 --this.gold;
}
Mine.prototype.digmany=function(levels) {
 if(this.gold<levels) throw new Error("Too poor");
 this.depth+=levels;
 this.gold-=levels;
}
Mine.prototype.search=function () {
 this.gold+=Math.round(Math.random()*3*(this.depth-this.searchdepth));
 this.searchdepth=this.depth;
}
Mine.prototype.print = function () {
 console.log(`depth(${this.depth}) searchdepth(${this.searchdepth}) gold(${this.gold})`);
}

Mine.script = function(func) {
 var mine = new Mine();
 func.apply(mine);
 mine.print();
}

Mine.script(function () {
 this.print()
 this.dig()
 this.print()
 this.search()
 this.print()
 this.dig()
 this.print()
 this.search()
});

See. The same general premise is possible but the end result isn't as cool. Though there are other context manipulating techniques that can be used to get rid of the this leading every line. I could have run the function in a context where the object was the global object. But this matches the strategy ruby is using. And importantly there isn't much we could do to get rid of the parenthises. And with that it reads like it's just code instead of a context specific language.

I only scratched the surface. Ruby also gives us more advanced resources to help in scenarios where javascript wouldn't. You can even set it to catch when an undefined method is called so you can interpret what to do with the line manually. That means your DSL can have infinite methods while only defining a finite number of them manually.

Comment preview