[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-... [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@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
V Thu, 13 Feb 2020 11:47:05 +0100 Ancor Gonzalez Sosa <ancor@suse.de> napsáno:
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.
I have to say that I really like this part of language (type checking). It is not annoying bureaucracy like in java, c++ or ycp. It just guess majority of types and only exception is empty containers or getters/setters where it have to be written. I also like that nil have to be explicitly enabled for given variable. So if you specify something like String, you can be sure it is string and not nil ( yes, I am looking at you ycp ). And same for libraries, you can safely ignore nil as it say user to not send nil there if you do not enable it explicitly. Now for less bright side of type system: 1. it is check only for usage of code. So you can have in library code that is not checked until it is called. So if you write code that call library, it is checked. If you write library that is not called, checking is less strong. 2. there is type hole and it is called Pointers and it is explicitly weak in type system. But I think it is not much used beside bridging to other languages. So in general if ruby3 type checking will look similar, I think it would be good change and I enjoy trying new language that is familiar, but also trying to address some weak points of ruby. Josef -- To unsubscribe, e-mail: yast-devel+unsubscribe@opensuse.org To contact the owner, e-mail: yast-devel+owner@opensuse.org
participants (2)
-
Ancor Gonzalez Sosa
-
Josef Reidinger