Mailinglist Archive: yast-devel (35 mails)

< Previous Next >
[yast-devel] Hack Week report: learn Crystal by porting part of YaST to that language
During this Hack Week I wanted to experiment with Crystal doing
something concrete. See more details at [1] and [2]. Josef Reidinger
joined the project as well.

So first, the TLDR;

1) We migrated quite some non-trivial code from Ruby to Crystal. Porting
Ruby+RSpec code to Crystal+Spectator was surprisingly smooth.

2) No, we don't have benchmarks to show the memory consumption or speed
improvement of the migrated code.

3) Then we tried to create Crystal bindings for libstorage-ng (a library
written in C++). The experience was not that good, we failed.

4) I have been asked, so you plan to port YaST to Crystal? The answer is
no. Crystal is a great language but the ecosystem is far from the level
of maturity needed for a multi-platform LTSS system.

Now the long version...

For those who don't know what Crystal is. It's a statically type-checked
programming language that compiles into very efficient native code,
while looking very similar to Ruby. It has a VERY similar syntax
(although compatibility with it is not a goal) and a very similar
approach to object oriented programming (at least, in the part of it
everyone uses in a daily basis).

So back to (1). Porting code from Ruby to Crystal is often as simple as:

mv class.rb class.cr

Yes, really. It's surprisingly easy. Although Crystal is statically
type-checked, the compiler does a great job in inferring the types for
everything. That, together with union types and null reference checks,
makes everything really smooth.

But we wanted to challenge the language, so the code we ported included
many things I expected to be tricky to migrate, like defining operators,
using metaprogramming to add new class methods to the native numeric
classes, using Ruby's "const_get" to decide during runtime which exact
class to instantiate, using Ruby's "send" with a variable number of
arguments, defining and using mixins, holding references to objects that
could be of several classes with no common ancestor...

Some of those things just worked as-is in Crystal. With no change
needed. All the others were easy to implement using macros. Macros are
Crystal's answer to metaprogramming and they are a really powerful tool.
The approach is very different to Ruby metaprogramming, so this is the
part where a simple rename of the file or even some semi-advanced
automatic conversion wouldn't be enough to migrate. But macros
definitely look powerful enough to solve most of the things we currently
solve with metaprogramming in Ruby. Another nice surprise.

But we also wanted to port the RSpec testsuite. A testsuite that makes
good use of contexts, "let", mocking and stubbing. That is, things like
this:

describe Y2Storage::AutoinstIssues::List do
subject(:list) { described_class.new }

let(:section) { Y2Storage::AutoinstProfile::SkipListSection.new }
let(:issue) { instance_double(Y2Storage::AutoinstIssues::Exception) }

describe "#add" do
it "adds a new issue to the list" do
list.add(:exception, StandardError.new)
expect(list.to_a)
.to all(be_an(Y2Storage::AutoinstIssues::Exception))
end

it "passes extra arguments to issue instance constructor" do
expect(Y2Storage::AutoinstIssues::InvalidValue)
.to receive(:new).with(section, :size, "value")

list.add(:invalid_value, section, :size, "value")
end
end
end

Thanks to Spectator, this turned to also be almost a matter of renaming
the file. See the Crystal equivalent:

Spectator.describe Y2Storage::AutoinstIssues::List do
subject(:list) { described_class.new }

let(:section) { Y2Storage::AutoinstProfile::SkipListSection.new }

mock Y2Storage::AutoinstIssues::InvalidValue do
stub self.new(*args)
end

describe "#add" do
it "adds a new issue to the list" do
list.add(:exception, NilAssertionError.new)
expect(list.to_a)
.to all(be_an(Y2Storage::AutoinstIssues::Exception))
end

it "passes extra arguments to issue instance constructor" do
expect(Y2Storage::AutoinstIssues::InvalidValue)
.to receive(:new).with(section, :size, "value")

list.add(:invalid_value, section, :size, "value")
end
end
end

Almost identical, isn't it?

In short, the experience in migrating code and tests could hardly be better.

Now let's skip the second point and jump to (3) - creating bindings for
libstorage-ng. The experience there was not that good. We only found one
usable generator of C++ bindings for Crystal[3]. It's being currently
used to create Qt5 bindings, but is the typical project that relies on a
single person which has a single use-case (the mentioned Qt5 bindings).
It almost works for the general case, so it could really be a perfect
tool with some investment from other sources other than his current only
developer. But is still not there.

And no, Swig[4] does not support Crystal as output language. Looking at
the Crystal code generated by bindgen and at the Ruby code generated by
Swig, implementing Swig support for Crystal would be doable, but I don't
think there is anyone even trying right now.

Which leads us directly to (4). After a week of playing with Crystal I
clearly see it is a perfect language to migrate away from the Ruby
runtime. The language retains many of the nice things we love from Ruby
and it adds some extra goodies. And porting Ruby code to it is a
pleasure. If the YaST Team managed to migrate from YCP to Ruby, doing it
from Ruby to Crystal would be 20 times easier.

But YaST is not a simple project that has to run in a limited set of
servers. We must be able to ship a product that can be executed in x86,
PowerPC, s390, ARM and whatnot. And we need to be sure the tool used to
generate C++ bindings is properly maintained for the following X years.
We also need to feel that in the future we will be able to find a
library for whatever task (parsing a new file format, for example).

In short, we need solid ground to be able to provide LTSS support for
something written in Crystal. All that will not happen without a
sustained investment in the language itself. Since I don't see SUSE
hiring two people to work full-time in Crystal and its ecosystem for the
following couple of years, I wouldn't advise any project that needs to
be shipped in SLE to migrate from Ruby to Crystal.

But for smaller projects that only need to run in a controlled
environment... give it a try!

EOREPORT

[1]
https://hackweek.suse.com/projects/learn-crystal-by-porting-part-of-yast-to-that-language
[2] https://github.com/ancorgs/y3storage
[3] https://github.com/Papierkorb/bindgen
[4] http://www.swig.org/

--
Ancor González Sosa
YaST Team at SUSE Software Solutions
--
To unsubscribe, e-mail: yast-devel+unsubscribe@xxxxxxxxxxxx
To contact the owner, e-mail: yast-devel+owner@xxxxxxxxxxxx

< Previous Next >
Follow Ups