Ruby 🤝 Crystal? An Introduction to Gloss
First things first, thank you for dropping by, and for allowing me a few moments to introduce a recent project of mine. It’s called Gloss, and I have been doing some work on it here and there for the past few months or so. Perhaps my best explanation at the moment would be the newborn love child of Ruby and Crystal:
# greeter.glenum Language
English
French
JavaScript
Italian
endclass Greeter
attr_reader :name, :language def initialize(@name: String, @language); end def perform
puts greeting
end private def greeting: String
hello = case language
when Language::French
"salut"
when Language::Italian
"ciao"
when Language::JavaScript
"DOMContentLoaded"
else
"hello"
end
"#{hello} #{name}"
end
endGreeter.new("World", Language::English).perform
You’ll be over-engineering a “Hello World” app in no time!
Let me try to explain!
I’ve been writing Ruby for a couple of years, and like many Ruby developers have at some point, I came across Crystal a while back. Its appeal is coming from Ruby is undeniable, for sure —it boasts simple and familiar syntax, [largely] optional type annotations, macros, enums, tuples — just to name a few of its features. Safe to say, this got me thinking:
What if you could use Crystals “front end” as a new interface for writing Ruby code?
It could allow for more nuanced expression, opening up the possibility for some of the features above. It would mean you could still use all the gems you know and love, with great cross-platform support, flexibility and ease of working with Ruby.
Where did I even begin?
Let’s be realistic here — although these languages look quite similar, they operate very differently behind the scenes; this was unfortunately not going to be just a question of “plugging” Crystal into Ruby and being done with it; it would also involve having to build out the features to fill the extra space provided by the new interface. But spurred on by my naivety, I thought this could be worth a crack anyway.
Fortunately, I already had a few building blocks to work with — of course I could have taken a look at Crystal’s own parser, and adapt it to suit my needs (not all of Ruby’s features are supported by Crystal).
Another stroke of good luck was the development of RBS — a relatively new addition to Ruby, as of Ruby 3.0.0 (2020–12–25). For those unfamiliar, RBS provides an interface for defining the structure of Ruby programs, ie. “These are my program’s classes, and these are their methods”, to simplify. However, I am not exactly using RBS in the way it is currently intended, so there have had to be a few pieces of gaffer tape here and there to get things to work as I wanted — but hopefully jsut some growing pains!
Hand in hand with RBS came Steep, though, once again, I wasn’t exactly using this in the conventional manner. The gem is currently designed to be used from the command line or from its language server, I at least found myself able to borrow large elements from it, to provide the real mechanics behind the type checking.
How can I get started?
I welcome you to play around and investigate for yourself — but be warned that things are still very much in the early stages! On that note though I will add that Gloss itself is now largely written in Gloss, and can compile itself (it helps if you know which bugs to look out for and avoid 😅) — so there may still be some use for it at this stage!
First thing’s first, you will need a Crystal installation to be able to install Gloss. If you have trouble setting it up on your box, there is fortunately a Docker image available with Crystal, Ruby and Gloss preinstalled.
Whether you use Docker or not, you will want to add gloss
to your project’s Gemfile
:
source "https://rubygems.org/"
group :development do
gem "gloss"
end
And then run bundle
to install it. If you are using Docker, it will be pre-installed so this should be a no-op; otherwise, it may take a minute or so for the native extensions to be compiled.
Once installation is complete, you can run gloss init
— this will simply create a .gloss.yml
file in your project’s directory.
This will have 2 properties of interest for now, which you should set:
src_dir: src
entrypoint:
Firstly, src_dir
refers to the directory where the gloss files to be compiled are to be found. By default, this is src
(but, for instance, you could just set it to .
if you wanted to), and we can leave it at that for now.
Second, entrypoint
refers to the file which you will run when starting the app. In Ruby this is significant, because it allows Gloss to properly map out all of the require
and require_relative
calls, so that it can determine what is in scope. In this case, I will be setting it to src/hello.gl
Next, create a .gl file in your src_dir
mkdir src && echo "puts 'hello world'" > src/greeter.gl
Now time to build: Simply run gloss build
.
This will tell gloss to look for all of your .gl
files in your src_dir
, and it will build them and type check them, writing output into files in your project’s directory. Note how the structure of your src_dir
will be echoed in your main directory — that is to say, if you need a certain file to be loaded from the lib
directory at runtime, you could put it in src/lib/my_file.gl
, and it would be generated as lib/my_file.rb
when building.
Finally, run your app: ruby greeter.rb
That’s all for now! Stay tuned for more to come, including building your first command line app, working with gems, building and deploying your first web app, integrating with existing projects, and more.