Recently I posted ‘Doing less’. Tl;dr: I wondered why we (as tech-society) seem to be thrilled about making inefficient round trips using AI for development, or chase each other to use typed languages, while we could be using more expressive programming languages instead. Instead of guessing human input, we could write untyped short scripts that detail every edge case carefully, but without extreme uncertainty of human language input nor the extreme preciseness of typed languages. Scripting, however, is scoffed at by Real Programmers, but then why oh why do we AI?
Someone suggested I should try a different programming language (knowing that I’m a rubyist). Try Crystal, a language that shares performance characteristics of other compiled languages like C and Rust (not always in the top regions, but close). And although I heard of it a long time ago, I kinda forgot about it, shifting my interests towards learning Rust instead (one day). But Rust is not as fun as ruby. Crystal is also not ruby, but closer? To test it I tried rewriting a very simple static site generator. And it worked well for me.
puts "Hello world"
The title is a one line crystal program. No unnecessary boiler plate criterium (which I like about ruby as well) is met.
Crystal comes with its own package manager shards, and it is based mostly on git-repo’s that you include. It is a YAML file, requiring that holds a bit between a package.json (it also contains the app’s name and some other metadata) and a Gemfile
in ruby.
I needed some Markdown parsing and front matter parser (for metadata) and it was easy to find using Shardbox. YAML parsing, templating and more was already covered by the standard libraries. I wasn’t disappointed.
ECR is what replaces ERB. One thing to understand though, is that it is has to be precompiled. So no dynamic inclusion based on a variable. But it is simple and effective. I was looking shortly at Blueprint which reminded me of Phlex in ruby, but for now ECR was sufficient.
Everything is an object, which is what I enjoy about ruby, and I can even extend core objects:
class String
def pluralize
self + "s"
end
end
"demon".pluralize # demons
Of course you should not do this, but it is nice that it is possible, especially if you want a behaviour on an object that you’re missing from e.g. Ruby or Rails’ ActiveSupport.
To successfully pre-compile, Crystal is actually a typed language. The fun part: you might not even notice. Sometimes you have to define the expected type (e.g. when defining object properties). In my simple use case it hasn’t been a big problem, although my initial plan of converting a YAML structure in a Hash turned out to be more complicated than expected, hence I decided to rely on the YAML::Any
-class as front matter. The proper way might have been to iterate over the front matter key values to expected classes, but for my little experiment there was no need to bother.
Integer#times
Yes, you can do:
10.times do |i|
p i
end
They explicitly write in their Crystal for Rubyists-documentation:
Crystal is a different language, not another Ruby implementation.
But in general it feels much like it. You can even extend the base classes, allowing one to make necessary adjustments when needed. The removal of synonyms (it only supports Enumerable#find
, Enumerable#map
, and Enumerable#size
for example (and not select
& count
)) is fine by current me, but would have bothered me in the past. And Crystal only does double quoting, but that’s my preferred style anyway.
Also no attr_accessor
for classes, but property
, which is a much better method name anyway.
I haven’t spent much time exploring this, developer happiness was more important to me here (I try to follow best practices). On the other hand this is what is so attractive about Rust. Which I’ll return to.
Crystal doesn’t hit the marks for popularity / community size, but I think it fits a nice niche of compiled languages that are actually fun and easy to write, with great performance.
The biggest problem with low popularity is that you might be the first person to run into issues. With more popular languages, many problems have already been solved and/or worked around properly (eying towards aforementioned safety issues for example).
It may be slightly less flexible than ruby, and has a much smaller ecosystem, but in case I need to write something fast and simple, I might choose to use Crystal more. The language hits quite a few high marks. And (esp. coming from ruby) the learning curve is smooth.
require "markd"
require "front_matter"
require "yaml"
require "ecr"
require "html"
STATIC_FILES = ["turbo.js", "stylesheet.css"]
class String
def pluralize
self + "s"
end
end
class Page
property contents : String
property meta : YAML::Any
property title : String
property kind : String
property filename : String
def initialize(contents, meta, filename)
@contents = contents
@meta = meta
@title = meta.as_h.fetch("title") { "Geen titel" }.to_s
@kind = meta.as_h.fetch("kind") { "article" }.to_s
@filename = filename
end
def path
File.join(kind.pluralize, @filename, "/index.html")
end
def file_path
File.join("public", path)
end
def write
Dir.mkdir_p(File.dirname(file_path))
File.write(file_path, to_full)
end
def to_full
# we can't make this dynamic; ECR is inlined during compilation
if kind == "article"
ECR.render("crystal_layouts/article.ecr.html")
else
ECR.render("crystal_layouts/default.ecr.html")
end
end
end
pages = [] of Page
Dir["content/**/*.md"].each do |filename|
FrontMatter.open(filename) do |front_matter, content_io|
contents = Markd.to_html(content_io.gets_to_end)
filename = File.basename(filename, ".md")
page = Page.new(contents, YAML.parse(front_matter), filename)
pages << page
end
end
pages.each do |page|
page.write
end
def link_to(text, url)
"<a href=\"#{url}\">#{HTML.escape(text)}</a>"
end
# create index
sorted_articles = pages
contents = ECR.render("content/index.html.ecr")
title = "Waarom links?"
STATIC_FILES.each do |file_name|
File.copy(File.join("content",file_name), "public/#{file_name}")
end
File.write("public/index.html", ECR.render("crystal_layouts/default.ecr.html"))
Enjoyed this? Follow me on Mastodon or add the RSS, euh ATOM feed to your feed reader.
Dit artikel van murblog van Maarten Brouwers (murb) is in licentie gegeven volgens een Creative Commons Naamsvermelding 3.0 Nederland licentie .