I recently started toying with Gradual typing in Ruby.
Of course, Ruby is already typed: messages are dispatched to objects depending on their type. That’s dynamic typing, enforced at runtime.
But for larger programs, it can be useful to have some static type-checking, that can be enforced by a type-checker without running the whole program.
Enter type annotations. By adding some explicit informations about the expected types in our program, a type-checker will be able to catch some errors using a static analyzer.
What does it look like?
Gradual typing is not standardized in Ruby yet (although some efforts are ongoing). So there are different tools available. Currently, the best way to add gradual typing to Ruby programs seems to be Sorbet.
Here’s a simple Sorbet example, using an existing Ruby function:
def to_hex(i)
"%x" % i
end
We can add type informations to this function using the sig function decorator:
extend T::Sig
sig {params(i: Integer).returns(String)}
def to_hex(i)
"%x" % i
end
Granted, the syntax is a bit weird. But it has the merit of being valid Ruby code, which allows it to be accepted by the standard Ruby parser without modifications. And like many syntaxes that seem unusual at first, our eyes quickly get used to it (hi, Objective-C square brackets).
Letting “Gradual” shine
Now, the neat thing with gradual typing is that you don’t have to provide type informations everywhere. This is useful in many ways.
First, you may start adding type checks to an existing codebase. In that case, declaring all types from the start can be a daunting task. Fortunately, in the absence of types, the type-checker will consider that we know what you’re doing. Which means we can start adding a few types here and there, and already have useful type-checks – but the parts of the program which are still type-annotations-free will not results in warnings or errors.
Second, a lot of Ruby elegance and fun comes from its dynamic nature. Sometimes the most elegant way to express some code is to use dynamic method resolution, or other dynamic-oriented constructs which cannot be type-checked. And this is okay! In that case, gradual typing means you have an escape-hatch: as types are not mandatory, they just won’t be type-checked. We can get the benefits of type-checking for 95% of the code, and still use neat dynamic features in the remaining 5%.
And last, at times it can be useful to just experiment and prototype some code quickly, to see how the structure would look. In these cases, you won’t have to fight your way through types: you can just omit type annotations, and quickly let the code flow without thinking too much about production-ready reliability.
Experimenting with a standalone Ruby script
I’ve never used Sorbet before, so I wanted to start small, and get used to the type system.
Fortunately, the Sorbet website provides an online playground: just type in some Ruby code, add some type annotations, and the type checker will start telling you what’s right and wrong with the types you provided. Neat.
I used a standalone Ruby script I wrote some times ago. This script reads assembly source code and debug symbols from the local filesystem, and infers from this the probable location of more debug symbols. Here the initial version of the script, and the final version after finishing adding types.
When I copy-pasted the script, without adding anything yet, Sorbet immediately told me about two errors: <function> is not available on NilClass (https://srb.help/7003).
Wow: it detected that in two different places, my code was sending a message to a potentially nil object. And gave me an URL to learn more about the issue.
How to fix this? I followed the URL, and a well-written document explained me that I could either:
- Add a
nil-check before sending a message to the object;
- Or wrap the object in
T.must(…), to tell the type-checker “Trust me, this will never be nil”.
So the two reported errors were easy to fix. I was quite impressed that Sorbet found two relevant mistakes without even starting to add types. And even more impressed that it not only does nil-checks, but also type propagation (that is, when some code checks if a value is nil, Sorbet considers that after this point the variable can not longer be nil).
Adding types
After this, I started adding type annotations to a single method (a constructor). That was easy enough: just a matter of adding the correct sig {…} incantation.
But right after that, Sorbet told me about a new error: my signature stated that the method argument was a String, but elsewhere I was calling the constructor with a T.Nilable(String) – that is, an object that may be nil. Interesting. Like before, to fix it, I had to add the proper nil check before calling the constructor.
I then gradually added type annotations to more methods, and found it almost fun. I had the feeling that I was strengthening my program, and uncovering the hidden assumptions that had been there before.
All of this went rather smoothly (except having to convert Ruby Structs, unsupported by Sorbet, into T::Structs). The weird syntax quickly became bearable, and eventually even read like being a part of the documentation.
Refactoring
In the end, this even led me to write better code. For instance, consider this function :
class Address
def self.from_string(str)
bank, offset = str.split(':')
self.new(bank.to_i(16), offset.to_i(16))
end
end
It extracts the two components of a semicolon-separated string – like 03:4A2F.
When adding type annotations, Sorbet initially told me “Hey, offset.to_i(16) is not valid on NilClass”. Because of course, it detected that if the input string is badly formatted, offset may be nil.
So I quickly wrapped the value in T.must(…), to silent the warning away. After all, there’s not so much we can do to prevent badly-formatted input; crashing at runtime seems a sensible option.
class Address
sig {params(str: String).returns(Address)}
def self.from_string(str)
bank, offset = str.split(':')
self.new(T.must(bank).to_i(16), T.must(offset).to_i(16))
end
end
But wait, there’s better than crashing at runtime: and that’s “crashing at runtime with a meaningful error message”. What if we rely on nil-propagation to write instead:
class Address
sig {params(str: String).returns(Address)}
def self.from_string(str)
bank, offset = str.split(':')
raise "Invalid address format" if bank.nil? || offset.nil?
self.new(bank.to_i(16), offset.to_i(16))
end
end
Nice: a bad input now gives us a readable error message. And we can even remove the T.must checks, because, thanks to nil-propagation, Sorbet is now sure that offset.to_i(16) is not called on nil.
So far
After toying with the Sorbet’ playground, here are my first impressions:
The good
- The online Playground
- The expressive error messages
- Nil-checking and nil-propagation
- The quality of the documentation
- Questions concerning Sorbet appear easily in search engines
The bad
- Needing to rewrite some Ruby code (like Structs)
The ugly
What’s next
For now I haven’t tried to type-check a program locally, nor to type-check code that relies on external gems.
So my next step is probably to add some minimal type-checking to a small Rails app, and see how Sorbet’s tooling deals with the many dynamic constructs of the framework.
Il y a quelques mois, j’ai fini La zone du dehors d’Alain Damasio. J’en suis ressorti avec des impressions mitigées, qui ont fini par se décanter. Pour une bonne partie, j’ai eu l’impression que la narration avait du mal à savoir où elle allait, que les séquences se juxtaposent dans une certaine confusion thématique – et que même si j’ai du mal avec les arcs narratifs lisses et stéréotypés, un peu de cohérence ne fait pas de mal non plus.
Mais y’a quelques points qui m’ont bien intéressés. En vrac :
Le vitalisme
Une des choses que La zone du dehors a clarifié pour moi, c’est que Damasio me semble attaché moins à l’anarchisme qu’à un genre de vitalisme. “Oui, tout foisonne, tout bouillonne, ça pose plein de problèmes mais c’est génial”.
J’ai l’impression que cette appréciation du vitalisme se trouve aussi dans Les furtifs, et dans une moindre mesure dans La horde du Contrevent – mais aussi beaucoup dans ce que Damasio a pu écrire sur la zad de Notre-Dame des Landes il y a quelques années.
Et ça m’aide à comprendre pourquoi tout ne me parle pas dans les récits de Damasio : le vitalisme c’est sa came à lui, et pas trop la mienne. Et que y’a aussi d’autres choses que ça dans l’anarchisme (genre des courants qui sont plus “l’ordre moins le pouvoir”). Et que c’est bien aussi.
Le patriarcat
Ouch. La zone du dehors, en terme de féminisme, c’est tendu. Et j’ai l’impression que c’est un point aveugle des écrits de Damasio depuis longtemps.
Je renvoie ici à la série d’articles (encore en cours) de Mélissa et Lunar, qui ont fait une analyse des Furtifs sous ce prisme, avec plein de dataviz : https://dérivation.fr/furtifs/.
La société de l’évaluation
Les passages sur la société de l’évaluation m’ont bien parlé. En bonne partie parce que pour moi ça donnait du sens à ce qui se passe dans le néo-management qui se retrouve fréquemment dans la startup-nation.
Vous savez, c’est cette forme de management à la cool où l’évaluation est omni-présente. Toutes les deux semaines, un one-to-one avec un manager. Toutes les six semaines, faire une liste d’objectifs hyper-précis à atteindre – et définissez des cibles chiffrées. Tous les six mois, une “review à 360°” où vous êtes évaluées par des managers et des pairs – dans l’espoir d’obtenir une augmentation qui ne viendra jamais. Et en permanence, devoir participer aux nombreuses évaluations de vos collègues. Mais c’est cool, parce que c’est vous-même qui définissez vos objectifs, tout ça.
Évidemment, tout ça ressemble fortement au Clastre, le grand système de classement social général de La zone du dehors. Et ce qu’en dit Damasio dans la grande tirade deleuzo-foucaldienne, c’est que l’objectif de tout ça est de re-former la personnalité des individus.
Il s’agit bien sûr d’une part les individualiser (chacun essaie de suivre ses objectifs le nez dans son guidon, ça atomise), mais même au sein de l’individu, de découper chaque individu en compétences, en traits de caractères désirables, de diviser l’individu lui-même. Et ensuite l’évaluation des traits permet de dissoudre en partie l’individu, et à la place d’encourager les traits désirables par l’entreprise / l’État / etc.
Au même moment, dans ma vie, j’avais eu vent de discussion dans une startup de la fintech française où des managers commentaient des traits de personnalité. Par exemple « Finalement, toi tu sais défendre tes idées, mais tu respectes aussi l’autorité quand il faut », des choses de ce genre.
Mais en fait c’est des discours complètement performatifs : ça sert pas à remarquer ce qui est bien, mais à dégager un trait, et à dire aux individus « Ça serait bien que tu cultives ça, c’est valorisé par le management, ça t’aidera à avoir une augmentation aux prochains 6 mois. »
Bref, cette idée de “Le micro-management (même auto-organisé) sert à non seulement individualiser, mais séparer les individus eux mêmes en plusieurs traits, et à les modeler” ça m’a semblé éclairant.
Ce texte est une traduction de « Some people », de Jason Kottke.
Certaines personnes se sentent angoissées et impuissantes.
Certaines personnes s’ennuient.
Certaines personnes sont isolées en confinement et se sentent seules.
Certaines personnes se rendent comptent que l’Après sera très différent de l’Avant.
Certaines personnes profitent du temps supplémentaire avec leurs enfants et quand ça sera fini, ce temps leur manquera.
Certaines personnes viennent de sortir de leur douzième créneau d’affilé à l’hôpital et ne peuvent pas embrasser leur famille.
Certaines personnes ont mangé à leur restaurant préféré pour la dernière fois et ne le savent pas encore.
Certaines personnes sont mortes du coronavirus.
Certaines personnes ne peuvent pas s’empêcher de lire les nouvelles.
Certaines personnes n’ont pas les moyens d’acheter du savon.
Certaines personnes apprennent à faire du pain.
Certaines personnes travaillent de chez elles tout en essayant de faire l’école à la maison.
Certaines personnes sont des parents seuls qui essayent de travailler de chez eux tout en essayant de faire l’école à la maison.
Certaines personnes ont du mal à boucler les fins de mois et la prochaine paye ne viendra pas.
Certaines personnes sont inaptes à la fonction de président.
Certaines personnes ont quitté la ville pour leurs résidences secondaires.
Certaines personnes ne peuvent pas faire les courses parce qu’elles sont des personnes à risque.
Certaines personnes ont perdu leur travail.
Certaines personnes n’arrivent pas à dormir.
Certaines personnes regardent gratuitement des opéras en ligne.
Certaines personnes sont en quarantaine depuis plusieurs semaines.
Certaines personnes ne peuvent pas télé-travailler.
Certaines personnes ont attrapé le coronavirus et ne le savent pas encore.
Certaines personnes sont trop angoissées pour se concentrer sur leur travail.
Certaines personnes n’ont pas les moyens de payer leur loyer du mois prochain.
Certaines personnes continuent à se réunir en grands groupes.
Certaines personnes prennent de vrais risques pour sauver nos vies.
Certaines personnes n’ont pas acheté assez de solution hydro-alcoolique.
Certaines personnes ont acheté trop de solution hydro-alcoolique.
Certaines personnes n’ont plus accès à leur thérapeute.
Certaines personnes ne peuvent pas aller travailler mais sont toujours payées par leur employeur. Pour l’instant.
Pour certaines personnes, le principal souci est de décider ce qu’elles vont regarder ensuite sur Netflix.
Certaines personnes se portent volontaires.
Certaines personnes ont une entreprise qui va faire faillite.
Certaines personnes se rendent comptent que les enseignants sont formidables.
Certaines personnes commandent à emporter aux restaurants du coin.
Certaines personnes voudraient vraiment juste un câlin.
Certaines personnes n’arrivent pas à convaincre leurs parents âgés de prendre tout ça au sérieux.
Certaines personnes s’inquiètent de leurs investissements en bourse pour leurs vieux jours.
Et certaines personnes n’ont jamais eu d’investissements.
Certaines personnes vont être confrontées à plus de violences domestiques.
Certaines personnes vont tomber malade ou se blesser et auront plus de mal à accéder à des soins médicaux.
Certaines personnes ne peuvent pas acheter la nourriture dont elles ont besoin parce que les produits acceptés par les aides sociales alimentaires ne sont plus en rayon.
Certaines personnes ne veulent pas arrêter de faire la fête.
Certaines personnes ont perdu leur solution de garde d’enfant.
Certaines personnes font tout ce qu’elles peuvent pour rester calmes et garder l’espoir et ça ne marche pas.
Certaines personnes regardent Contagion et jouent à Pandémie.
Certaines personnes ne savent pas ce qu’elles vont faire.
Certaines personnes sont surchargées de conseils sur comment travailler depuis chez soi.
Certaines personnes mangent ou boivent trop.
Certaines personnes pensent à l’après.
Certaines personnes sont contrariées de ne pas pouvoir voyager.
Certaines personnes sont en manque de sexe.
Certaines personnes prévoient de jardiner plus cette année.
Certaines personnes ne vont pas voir leur famille pendant des mois.
Certaines personnes se déconnectent pour garder la tête froide.
Certaines personnes n’arrivent pas à voir le bout du tunnel.
Certaines personnes vont se rendre compte qu’il faut qu’elles rompent avec leur conjoint.
Certaines personnes chantent Imagine all the people.
Certaines personnes ne sont pas dans cette liste.
Ces expériences sont toutes celles de vraies personnes, tirées de journaux, des réseaux sociaux, et d’amis. Courage : vous n’êtes pas la seule personne à passer par ce que vous vivez en ce moment. Mais soyez attentionné : tout le monde ne passe pas par la même chose que vous. Même si en dernier lieu, tous nous sommes affectés par ces mêmes évènements.
Jason Kottke