際際滷

際際滷Share a Scribd company logo
Writing your own DSL
Yes, it is that easy!
Writing your own DSL
Who am I?
 Rob Kinyon
 @rkinyon
 rob.kinyon@gmail.com
 Devops lead for many years
 Developer in Ruby, Python, Perl, JS, and
others.
What is a DSL?
 Domain-Specific Language
What is a DSL?
 Domain-Specific Language
 Language - A vehicle for communication
What is a DSL?
 Domain-Specific Language
 Language - A vehicle for communication
 Domain - A restrained set of concepts
What is a DSL?
 Domain-Specific Language
 Language - A vehicle for communication
 Domain - A restrained set of concepts
 Specific - Limited to.
What is a DSL?
 Domain-Specific Language
 Language - A vehicle for communication
 Domain - A restrained set of concepts
 Specific - Limited to.
 No, really. :)
Language and Communication
 Communicate in one direction
 Author -> Executor
Language and Communication
 Communicate in two directions
 Author -> Executor
 Author -> Maintainer
Language and Communication
 Communicate in three directions
 Author -> Executor
 Author -> Maintainer
 Specifier -> Author
Language and Communication
 Communicate in four directions
 Author -> Executor
 Author -> Maintainer
 Specifier -> Author
 Author -> Verifier
Language and Communication
 Communicate in MANY directions
 Author -> Executor
 Author -> Maintainer
 Specifier -> Author
 Author -> Verifier
 Author -> Teammate(s)
 Developer -> Sysadmin/Devops
  ->
Language and Communication
 Communicate in MANY directions
 Author -> Executor
 Author -> Maintainer
 Specifier -> Author
 Author -> Verifier
 Author -> Teammate(s)
 Developer -> Sysadmin/Devops
  -> 
The ONLY
computer
Language and Communication
 Communicate in MANY directions
 Author -> Executor
 Author -> Maintainer
 Specifier -> Author
 Author -> Verifier
 Author -> Teammate(s)
 Developer -> Sysadmin/Devops
  -> 
All humans
Language and Communication
 Communicate in MANY directions
 Author -> Executor
 Author <-> Maintainer
 Specifier <-> Author
 Author <-> Verifier
 Author <-> Teammate(s)
 Developer <-> Sysadmin/Devops
  <-> 
All human
communication
is two-way
Domain-Specific
 Eskimos supposedly have 50+ words for
snow
 Depends on how you count it
 Saami has 1000+ words dealing with
reindeer
 snarri - a reindeer with short, branched horns
 busat - a bull with a single, large testicle
Domain-specific : Busat
Busat - The quality of having
appropriately-specific expressiveness
for the domain.
DSLs you already use
 SQL
 set manipulation DSL
 CSS
 tree-visitor-defining DSL for setting metadata
 HAML
 HTML-definition DSL
 Bash
 A crappy way of issue shell commands with logic
Places for a DSL
 Packaging and orchestration
 most devops/operations activities
 Configuration file generation
 web servers
 monitoring
 datastores
 Configuration value management across environments
 Anything extremely complicated (such as SQL)
 Anything repetitive (such as CSS)
Reasons for a DSL
 Let the important things shine
 General-purpose is overly-verbose
 Bugs hide in boilerplate
 Non-developers can read and comprehend
 And maybe even propose changes through PRs?
Reasons for a DSL
DSL is to Ruby
as
Ruby is to Java
Writing a DSL
 Three passes
 Parsing
 Validation
 Production
Writing a DSL - Parsing
 DSL::Maker for parsing
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
Car = Struct.new(:make, :year, :engine)
Engine = Struct.new(:hemi)
class VehicleDSL < DSL::Maker
add_entrypoint(:car, {
:make => String,
:year => Integer,
:engine => generate_dsl({
:hemi => Boolean,
}) do
Engine.new(hemi)
end,
}) do |*args|
default(:make, args, 0)
Car.new(make, model, engine)
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
#!/usr/bin/env ruby
require vehicle/dsl
filename = ARGV.shift ||
raise No filename provided.
vehicles = Vehicle::DSL.parse_dsl(
IO.read(filename),
)
# Do something here with vehicles
[
Car[
:make => Accord,
:year => 1990,
:engine => Engine[
:hemi => true,
],
],
Car[
:make => Civic,
:year => 2014,
:engine => nil,
],
]
. . .
truck F-150 {
year 1999
}
. . .
. . .
Truck = Struct.new(:make, :year, :engine)
. . .
class VehicleDSL < DSL::Maker
. . .
add_entrypoint(:truck, {
:make => String,
:year => Integer,
:engine => . . .,
}) do |*args|
default(:make, args, 0)
Truck.new(make, model, nil)
end
end
#!/usr/bin/env ruby
require vehicle/dsl
filename = ARGV.shift ||
raise No filename provided.
vehicles = Vehicle::DSL.parse_dsl(
IO.read(filename),
)
# Do something here with vehicles
[
. . .
Truck[
:make => F-150,
:year => 1999,
:engine => nil
],
. . .
]
Writing a DSL - Validation
 DSL::Maker for parsing
 DSL::Maker for validation
. . .
class VehicleDSL < DSL::Maker
. . .
add_validation(:car) do |car|
unless car.engine
return Cars must have an engine
end
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
. . .
class VehicleDSL < DSL::Maker
. . .
add_validation(:car) do |car|
unless car.engine
return Cars must have an engine
end
end
end
car {
make Accord
year 1990
engine {
hemi Yes
}
}
car Civic {
year 2014
}
#!/usr/bin/env ruby
require vehicle/dsl
filename = ARGV.shift ||
raise No filename provided.
# This raises the error
vehicles = Vehicle::DSL.parse_dsl(
IO.read(filename),
)
# Do something here with vehicles
Error: Cars must have an engine
Writing a DSL - Production
 DSL::Maker for parsing
 DSL::Maker for validation
 Youre on your own for production
Writing a DSL - Production
 Work from outside in.
 Parsing is done inside-out.
 Transform in a series of passes.
 Expand everything (its just data)
 Dont do anything irrevocable until the end
 Work in temp directories, stage everything
Conclusion
 DSL::Maker 0.1.0 is available right now
 Patches welcome
 100% test coverage
 Im blogging about this at http:
//streamlined-book.blogspot.com
 First post on the topic
Questions?

More Related Content

Writing your own DSL

  • 1. Writing your own DSL Yes, it is that easy!
  • 3. Who am I? Rob Kinyon @rkinyon rob.kinyon@gmail.com Devops lead for many years Developer in Ruby, Python, Perl, JS, and others.
  • 4. What is a DSL? Domain-Specific Language
  • 5. What is a DSL? Domain-Specific Language Language - A vehicle for communication
  • 6. What is a DSL? Domain-Specific Language Language - A vehicle for communication Domain - A restrained set of concepts
  • 7. What is a DSL? Domain-Specific Language Language - A vehicle for communication Domain - A restrained set of concepts Specific - Limited to.
  • 8. What is a DSL? Domain-Specific Language Language - A vehicle for communication Domain - A restrained set of concepts Specific - Limited to. No, really. :)
  • 9. Language and Communication Communicate in one direction Author -> Executor
  • 10. Language and Communication Communicate in two directions Author -> Executor Author -> Maintainer
  • 11. Language and Communication Communicate in three directions Author -> Executor Author -> Maintainer Specifier -> Author
  • 12. Language and Communication Communicate in four directions Author -> Executor Author -> Maintainer Specifier -> Author Author -> Verifier
  • 13. Language and Communication Communicate in MANY directions Author -> Executor Author -> Maintainer Specifier -> Author Author -> Verifier Author -> Teammate(s) Developer -> Sysadmin/Devops ->
  • 14. Language and Communication Communicate in MANY directions Author -> Executor Author -> Maintainer Specifier -> Author Author -> Verifier Author -> Teammate(s) Developer -> Sysadmin/Devops -> The ONLY computer
  • 15. Language and Communication Communicate in MANY directions Author -> Executor Author -> Maintainer Specifier -> Author Author -> Verifier Author -> Teammate(s) Developer -> Sysadmin/Devops -> All humans
  • 16. Language and Communication Communicate in MANY directions Author -> Executor Author <-> Maintainer Specifier <-> Author Author <-> Verifier Author <-> Teammate(s) Developer <-> Sysadmin/Devops <-> All human communication is two-way
  • 17. Domain-Specific Eskimos supposedly have 50+ words for snow Depends on how you count it Saami has 1000+ words dealing with reindeer snarri - a reindeer with short, branched horns busat - a bull with a single, large testicle
  • 18. Domain-specific : Busat Busat - The quality of having appropriately-specific expressiveness for the domain.
  • 19. DSLs you already use SQL set manipulation DSL CSS tree-visitor-defining DSL for setting metadata HAML HTML-definition DSL Bash A crappy way of issue shell commands with logic
  • 20. Places for a DSL Packaging and orchestration most devops/operations activities Configuration file generation web servers monitoring datastores Configuration value management across environments Anything extremely complicated (such as SQL) Anything repetitive (such as CSS)
  • 21. Reasons for a DSL Let the important things shine General-purpose is overly-verbose Bugs hide in boilerplate Non-developers can read and comprehend And maybe even propose changes through PRs?
  • 22. Reasons for a DSL DSL is to Ruby as Ruby is to Java
  • 23. Writing a DSL Three passes Parsing Validation Production
  • 24. Writing a DSL - Parsing DSL::Maker for parsing
  • 25. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 26. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 27. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 28. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 29. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 30. Car = Struct.new(:make, :year, :engine) Engine = Struct.new(:hemi) class VehicleDSL < DSL::Maker add_entrypoint(:car, { :make => String, :year => Integer, :engine => generate_dsl({ :hemi => Boolean, }) do Engine.new(hemi) end, }) do |*args| default(:make, args, 0) Car.new(make, model, engine) end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 31. #!/usr/bin/env ruby require vehicle/dsl filename = ARGV.shift || raise No filename provided. vehicles = Vehicle::DSL.parse_dsl( IO.read(filename), ) # Do something here with vehicles [ Car[ :make => Accord, :year => 1990, :engine => Engine[ :hemi => true, ], ], Car[ :make => Civic, :year => 2014, :engine => nil, ], ]
  • 32. . . . truck F-150 { year 1999 } . . . . . . Truck = Struct.new(:make, :year, :engine) . . . class VehicleDSL < DSL::Maker . . . add_entrypoint(:truck, { :make => String, :year => Integer, :engine => . . ., }) do |*args| default(:make, args, 0) Truck.new(make, model, nil) end end
  • 33. #!/usr/bin/env ruby require vehicle/dsl filename = ARGV.shift || raise No filename provided. vehicles = Vehicle::DSL.parse_dsl( IO.read(filename), ) # Do something here with vehicles [ . . . Truck[ :make => F-150, :year => 1999, :engine => nil ], . . . ]
  • 34. Writing a DSL - Validation DSL::Maker for parsing DSL::Maker for validation
  • 35. . . . class VehicleDSL < DSL::Maker . . . add_validation(:car) do |car| unless car.engine return Cars must have an engine end end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 36. . . . class VehicleDSL < DSL::Maker . . . add_validation(:car) do |car| unless car.engine return Cars must have an engine end end end car { make Accord year 1990 engine { hemi Yes } } car Civic { year 2014 }
  • 37. #!/usr/bin/env ruby require vehicle/dsl filename = ARGV.shift || raise No filename provided. # This raises the error vehicles = Vehicle::DSL.parse_dsl( IO.read(filename), ) # Do something here with vehicles Error: Cars must have an engine
  • 38. Writing a DSL - Production DSL::Maker for parsing DSL::Maker for validation Youre on your own for production
  • 39. Writing a DSL - Production Work from outside in. Parsing is done inside-out. Transform in a series of passes. Expand everything (its just data) Dont do anything irrevocable until the end Work in temp directories, stage everything
  • 40. Conclusion DSL::Maker 0.1.0 is available right now Patches welcome 100% test coverage Im blogging about this at http: //streamlined-book.blogspot.com First post on the topic