[go: up one dir, main page]

Showing posts with label YASL. Show all posts
Showing posts with label YASL. Show all posts

Thursday, December 31, 2020

How to Use Ruby Case Statements with === / Higher Order Lambdas / Pattern Matching

Happy New Year!

In this blog post, I will go over a practical example from a real project of how to use the Ruby `case` statement with Class implicit `is_a?` comparisons via `===` , higher order lambdas, and the new Ruby 3 pattern matching.

I just had to refactor some code in my new project YASL (Yet Another Serialization Library), which was originally in this form:


The when statements rely implicitly on the Class `===` method in object comparisons, which is implemented to test if `object.is_a?(SomeClass)` by default (e.g. `object.is_a?(Time)` for `when Time`) .

Although the code is pretty concise and readable, there is one big issue in it, mainly that `Date`, `DateTime`, and `BigDecimal` aren't loaded by default in Ruby, and in some older versions of Ruby, `Set` isn't loaded either. As such, that code would work in most cases, but bomb in cases in which comparison reaches `Date` and `DateTime` while not loaded via `require 'date'`, `BigDecimal` while not loaded via `require 'bigdecimal'`, or `Set` while not loaded via `require 'set'`. Unfortunately, pre-loading `Date`, `DateTime`, `BigDecimal` and `Set` could raise the memory footprint of the library for no important reason, so it is not desirable as a solution.

To circumvent this problem, I ended up comparing to class name strings instead of loaded classes by relying on higher order lambdas (in Ruby versions prior to Ruby 3) to achieve readable code without reliance on unwieldy if..elsif statements, albeit less pretty than the original code:


The reason that works is because `Proc` objects produced from lambdas have `===` implemented as simply `call(object)`. This ensures that a `Proc` object is first called with the class name(s) as strings (not actual loaded classes), returning another `Proc` ready to do the comparison on the particular object being tested (array of ancestor class names). Unfortunately, it is not very pretty due to the logic-unrelated lower-level `.call` methods. Thankfully, more recent versions of Ruby allow dropping them while keeping the `.` only for a more concise version:


This is prettier and more readable, but still a bit awkward. Can we drop the dot (`.`) entirely? Sure. Just switch parentheses to square brackets, and you could drop the dot (`.`) in newish versions of Ruby, resulting in more readable code:


That said, in the newly released Ruby 3, one could just rely on Array Pattern Matching via `case in` instead of `case when` to avoid higher order lambdas altogether:


That takes away the need to use higher order lambdas, which are a more complicated construct that is better avoided when possible. That said, YASL (Yet Another Serialization Library) still needs to support older Ruby versions, so I am stuck with higher order lambdas for now.

Just as a final note, keep in mind that the case statement could be eliminated completely by relying on Object Oriented Programming Design Patterns, such as Strategy. This is usually done on a case by case basis (no pun intended), and in this case I deemed it over-engineering to use the Strategy Pattern, but it's certainly an option on the table if needed in future refactorings.

In summary, case statements provide multiple ways to test objects through:

Have a Happy 2021!

Wednesday, December 30, 2020

Yet Another Serialization Library

Announcing YASL: Yet Another Serialization Library!

I know what you're thinking: "What?! Another serialization library!?! Why does it sound suspiciously like YAML? Isn't YAML enough?"

Good questions!

The short answer is YAML isn't available in Opal Ruby inside web browsers on the client-side, and Ruby Marshal raises errors by design whenever your objects reference unserializable objects like Proc.

The long answer is I needed a library that was written in Pure Ruby to ensure that it worked the same exact way in Opal, JRuby, and standard MRI Ruby so that I could use in network calls made by Glimmer applications, whether Glimmer DSL for SWT (JRuby), Glimmer DSL for Opal (Opal), or Glimmer DSL for Tk (MRI). Also, it had to silently ignore unserializable objects. Last but not least, developers are busy solving business domain problems, so the library has to require zero configuration. In other words, I don't want to fuss around with as_json methods or serializer configuration classes to manually specify attributes for JSON serialization. I just want serialization to work by passing objects in, period.

YASL took me exactly one week to write test-first till I reached the initial release. Not bad, right?

Don't get me wrong! When working strictly in standard MRI Ruby, I just use YAML. However, when building cross-Ruby apps in Glimmer that need to work in both desktop and web, YASL is the way to go!

Here is a quick intro taken straight out of the README. Enjoy!