← Ingestions

Ingestion f7d4157f extracted

Format
transcript
Kind
talk
External ID
1. Joel Drapper - Ruby has literally always had types - wroc_love.rb 2025.txt
Content hash
0c9cad4219de
Source at
2025-03-14 09:00
Manual extractions are temporarily disabled.

Extractions (2)

Status Model Tokens (in/out) Duration Cost Nodes/edges Read set (nodes/edges) Time
completed claude-opus-4-7
330,221 / 13,912
98,055 cached · 19,611 write
227.0s - 24 / 46 141 / 2 2026-04-18 06:46
failed claude-opus-4-7 RubyLLM::BadRequestError: You have reached your specified API usage limits. You will regain access on 2... 2026-04-17 16:18

Content

Yeah, that's all from the announcements


and now let's welcome our first speaker


Joel Drapper that will tell us about um


that Ruby always has a types had the


types and Joel himself enjoys meta


programming and he likes to go into


those dark places that usually don't


explore and he's really passionate about


that and also he's creator of flex and


quick


Thank


you. Thank


you. This is going to be pretty intense.


Um hopefully we can cover all the


material, but


uh thank you for thank you for having


me. It's great to be here. This is my


first ever


um France love RB. Is that okay?


[Applause]


Um, I practiced that like a hundred


times and I I still can't do it. Um, so


I'm here to tell you, well, actually,


let me just introduce myself. Um, if you


don't know me know me, my name is Joel


Drapper. Uh, I created the view


component library, Flex. Um, you find me


on Blue Sky. I also blog at


joel.drapper.me. Um, I also co-host the


Rooftop Ruby uh podcast. Uh, and you can


find me on GitHub. I do a lot of like


open source stuff.


So, so I want to convince you that Ruby


has literally always had types. And you


might have some very strong opinions


about types. Types in Ruby. I know


Rubists, we hate types.


Um, this is the the other title I was


considering. uh for this talk.


Um write less code, have fewer bugs


because ultimately like that's the goal


of everything that we're trying to do


here.


Um but let's go with uh let's go with


this because I want to convince you of


this. Um oh [ __ ] my talks are broken.


Oh no, that's part that's part of my


talk.


Um, so that's like actually genuinely


really triggering whenever I see that.


Uh, how many times have you seen this in


in


production? Like


never.


Um, I want to tell you a quick story


about um, this piece of code right here.


This isn't the exact code. It was a long


time ago, but this this captures it. Um,


this code is extremely extremely


dangerous. Can anyone tell me why?


Nope. Uh, that's not that's not SQL, but


that's a very very good guess.


Um, let's focus on this. So, yes, you


got it. Um, when you interpolate a


string like this, what you're really


doing is this.


Uh, and so if you got a method missing


error in production, you're the lucky


one, right? Because at least it's


stopped. But nil responds to


2s. Uh, and so let's look at this code


again. We take an email address. Uh, we


delete tickets. Um, we get the tickets


by querying for email. Is this? So our


our query is going to be this. if we


have a null email


address. And so, uh, when we send this


to Zenesk, what are they going to


do? They've basically got two logically,


there's two things that they could do.


They could give us no tickets or they


could give us all tickets. Um, and of


course, they gave us all the tickets.


Um, so this this code combined with a


bug in Rails, a bug in Pummer that was


triggered by some weird race condition


turned into a CVE in both of those. Um,


this this basically caused us to delete.


I think it was like maybe 4 to 10,000


Zenes tickets at Shopify before we


managed to stop it.


Um, and it's all because um, we didn't


have types, or at least that's what I'm


going to try and convince you, right? So


types are meant to fix this, right? Um,


and you're a rubist, so you probably


have a very strong opinion on this.


Probably most of you hate the idea of


types. Um, some of you might have tried


TypeScript, and maybe you even like it.


I happen to be one of those degenerates


who who loves TypeScript. I think it's


great.


Um,


and there have been a few attempts to


introduce static type checking into


Ruby. Um, Sorbet notably has has


probably gone the furthest. Um, and this


like this is where you kind of annotate


using Ruby pseudo Ruby code that doesn't


really execute and then they have a


compiler or like a a static analysis


tool that analyzes your code. Um,


there's also RBS which is the official


type annotation language and you can


either do that in separate files or you


can do it in


uh inline comments next to next to all


your methods. But RBS doesn't have a


type checker. So the best one out there


right now is steep um by Storo Matsum


Matsumoto. And


um like I applaud the effort that


everyone has put into these tools. Um


but I'm not entirely convinced that Ruby


is comfortable being typed like this.


Um I'm going to take a quick break.


So what are the issues with trying to


statically type


Ruby? Uh limited ability to catch


issues. So when you statically type um


type check something, you kind of need


to


know what all of the types are in the


entire system. Uh, so you basically need


to either be able to infer the types or


you need to actually have all of the


types annotated to be able to track it


through through the code. And Ruby, like


there's just so much Ruby code out there


that isn't already statically typed. And


I just don't think that the community is


going to fully get behind like writing


type annotations for everything. I just


don't think that's going to happen. So


introducing one of these tools into your


codebase um is a significant upfront


effort. know that they've done a great


job at Shopify um and I think is it


Stripe that uses this as well. There's


probably a few other big companies that


have done like done well from from using


Sorbet. Um I just don't think like the


teams of like 10 people working on a


reasonably large Rails app are ever


going to be able to, you know,


reasonably tackle this job of like


putting static types into into their


system. it's not compatible with


metarogramming or at least it's very


difficult to make it compatible. Um, and


yeah, you have to write about twice as


much code and the I don't know if this


is true or not. Obviously, like this is


like it's always the answer is always it


depends. But um some some people say


that um bugs are a function of code,


right? So the more lines of code you


have, the more likely that there are


bugs. It doesn't really matter what kind


of code you're writing, whether it's


type checkers. Um, more more code equals


more surface area for bugs to


appear. Um, so let's get like let's kind


of take a step back and think about like


what what really is a type? What does


that even mean? Um, and then think about


like how how are they useful and how can


we find some kind of like 8020


principle where like we what is there a


way that we can get the most out of


types without well doing the least


amount of work. Um, so my definition for


a type is very abstract and maybe this


is not technically correct but um I


don't think models need to be


technically correct to be useful. Um my


definition of a type is that it is a


description of a set of


objects. So it is not a set of objects


because you can have infinite types and


you couldn't possibly have an actual set


of infinite objects.


Um but it is just a description of one.


And working with a description of a set


of objects, you can


determine for some sets, some of them


are impossible to to calculate, but for


many sets, you can determine whether a


given object fits inside that set. Like


if this if this set did exist, would


would this object exist inside it? Would


this object be in that set?


And if we think about


uh this kind of like on the basis of


this definition when we look at Ruby I


think that Ruby has an interface that


really captures this idea and it's the


triple equals or case equality


interface.


In the context of Ruby, I think we can


say that any object that responds to


triple


equals is a


type. Let's look at a few


examples. So here we have a couple of


examples of classes. So with a class you


send triple equals you pass it an object


and it will tell you if the object is an


instance of that class or an instance of


a a subclass of that class. So this


holds holds up to that standard. What


about modules? Well, modules kind of


work the same way. They will tell you if


the uh if the object is either extends


the module or is a instance of an of a


class that includes the


module. What about ranges? So ranges


basically map this to their cover


method. So the range will tell you if an


object is covered by the


range. What about regular expressions?


They will check if the object matches


the


expression. What about procs? So procs


map the triple equals method to the call


method. Which means that you can


actually use a proc to define any


arbitrary predicate type. Right? A proc


that takes an object one parameter


returns truthy or falsy value. I would


say by my definition that it is a type


or at least it can be treated as a


type. Um and then objects themselves. So


strings. Let's go back. Um strings they


triple equal to other strings that are


equal in value to them. Um most other


objects either triple equals means equal


in value or the same object. The default


implementation on object and on your own


classes if you haven't overridden it


will be uh that triple equals is same


object. Um and actually that means that


we can treat these um the like objects


by default are what we would call um


unit types. They're types of exactly one


thing. Only one thing matches that type


um and nothing else does. And you've


actually already used these types. So


when you when you write a case statement


um you're not checking that the object


is equal to the class integer, right?


You're going to pass an integer. And so


what what what Ruby is doing is it's


going to send triple equals to um to


integer. And if that is true, it's going


to put I'm an integer. And if it's


false, uh it's going to move on and


check the next one. So you're already


using types. We just have Ruby, we just


didn't name these types. I think they're


name I don't know what they're named in


the Ruby source code, but


um also innumerable any, right? Um you


probably know that you can pass a block


to a numeral any but did you know you


can pass a type uses this


interface. Um same with all you pass a


type equals triple equals on the type


with the


object case statements um with pattern


matching. In fact any any pattern


matching is based on this as well. So in


this um it's kind of a bit of an obscure


example, but it's going to use the


triple equals method on array string um


to figure out


like what what this is.


Um now with this in mind that that we


can actually think about a type as just


being an object, that means that we can


create types, right? We can create


objects that their only purpose is


really to represent some type. um they


are their purpose is to describe a set


of objects. So here's an example of uh a


type calling array type that takes a


parameter type um and it will check that


the when you when you ask if something


matches that description, it will check


that it's an array and that all of its


values match the type that it's


parameterized by. So if we do this, we


can make uh we can make a new type. So I


can say I've got an array of strings or


I've got an array of integers. And then


we can use the triple equals on that


object to make assertions about our


objects. And we can define a shortcut


for this, right? We can define the


method. Um I like to use underscore and


then the name of the class if if it's


related to a class. The reason the


underscore is here is because there's


already a capital a letter um array


method in Ruby. And so this kind of


disambiguates, but it's also quite nice


because when you're like it looks kind


of weird at first, but you get used to


it. And then when you press underscore


um Ruby LSP like predicts all of your


types, which is kind of useful. Um so


this means that we can create this array


of strings type just by using underscore


array string and that also works with


the triple equals interface. I should


have given a false example here. Um so


let me introduce my library literal.


Um, literal is kind of it's got a bunch


of different tools, but the you have to


understand like how how literal


understands types first of all to be


already this thing that exists in Ruby.


Um, this like first class thing and then


it just introduces a bunch of these um


kind of generics like like I showed you


with the array. So, I'll just run


through some of these really quickly.


Um, union, this is the or type. So this


is going to say um this type represents


either a string or a


symbol intersection. Um this is the and


type. So this is going to be string and


matches the regular expression of being


a digit or one one or more digits. Um in


fact uh you can pass to to union and to


to intersection. You can pass as many


subtypes as you want as parameters.


contain uh constraint is like


intersection um in in terms of its


positional arguments but then it also


takes keyword arguments and what it will


do is it will call the keyword argument


on the object and then it will take the


return value and compare it to the type


that you passed in uh as the value to


that keyword argument. So here we've


composed a type that says it must be a


string. It must also be made up of


entirely of digits and its length, the


length of the string must be between


three and


255. And we've done that in one line of


code, barely any characters. There's


actually a um we implement underscore


string to essentially be a a composition


of this of a constraint that is already


set to be a constraint of a string and


then just pass everything through. I


think you see that later on. Um there's


also type interface. Um, so if you


really want to do duct typing, uh, you


can depend on interfaces. So you can


say, I don't care what like kind of


object it is. I just want to know that


it responds to length and push. So this


might be like a buffer or


something. Um, so array user, we already


saw this one. This is in literal. Tupole


is a bit like an array uh,


except uh, this is saying this is an


array of exactly two items. The first


item is an integer and the second item


is a


string. Um, hash takes key and value


types. So, this saying this is a hash


where the key is a symbol and the value


is a string for all of all of the


instances. This is a bit of a more


tricky example.


Um, the type we're looking at here is is


the type called deferred. Um, but I had


to do this big example to show you why


you would even need it. So, this is a


type that represents JSON data. So JSON


data can be you know a union of nil,


false, true, string, integer, float or


an array of JSON data. And since we


haven't yet defined JSON data, um using


deferred, which takes a block, um is a


way that we can actually create these


infinitely recursive types um that


reference themselves. So this is a I


believe accurate uh type for for


infinitely nested JSON data um because


hashes in JSON can be have to be have a


string as the key but then they can have


any data as their


value. Um but we have that one. So you


don't need to define it yourself. It's


just underscore JSON


data. So let me take a drink.


Okay, we're okay on


time. Uh, so why are types


useful?


Um, I can't remember what my next slide


is. Yeah, so types help us to make


basically invalid states in our code


impossible.


um we if if we're using types at


runtime, we're not going to be able to


kind of know statically that there are


no impossible states in our code, but


but we can know that we it's very


difficult for our code to get into


impossible states. Like before it gets


there, it's going to stop. So maybe


you're going to get an exception in


production. I talked about like how


you're the lucky one if you got a no


method error in production because


actually if you don't get an error, it's


much worse. Like something can actually


happen. it can be silently deleting


thousands and thousands and thousands of


emails and you don't know why. So


um two other tools that literal provides


are literal strruct and literal data and


we'll look at data


first. So these work in a very similar


way to the strruct and data objects that


you get built into Ruby except they take


props. So you uh you just subclass


literal data or literal strruct


depending on whether you want mutable or


immutable um value objects and you


define each property using this uh this


prop macro. So the first argument is uh


is the name of the property and then the


second argument is the type and this can


be any of the types we talked about any


composition of any type anything that


responds to triple equals. You could


throw a proc in here um literally


anything you want. Um, in this case,


we've actually made um coordinates that


cannot be invalid because we've we've


constrained our latitude and longitude


to floats and they're even constrained


to specific values of


floats. In fact, what I would do is


break this down even further and name


those types. So, latitude being a float


of - 90 to 90, let's give that a name.


Let's set it as a constant and then just


use that in our property. So, if there's


something else that references just one


of these properties and needs to check


it, like um I don't know why you would


have a job or whatever that takes just


the longitude, but you could you could


reference coordinates longitude as a


type somewhere else. I think I just find


that like naming these things can really


help. Um let's just look at some other


things that this interface provides you.


So by default um we use keyword


arguments but you can pass a third value


which is a symbol of positional if you


want a positional argument or a star. My


syntax highlighting is wrong but I


promise this is valid Ruby. Um a star if


you want to soak up a splat and that's


going to have to be some kind of array


type or it's going to error.


um default keyword arguments um star


star if you want a hash uh if you want


the you know a keyword splat or amperand


if you want to define the uh the block


argument. Um you can also pass a default


uh which makes these types uh these


properties optional. You don't have to


pass them in. Um the default has to be a


frozen object or you have to pass it


wrapped inside a proc. And the reason is


in case you say like default and you put


in an array or some an array literal and


then every single instance of this is


using the same array literal and now


you've got like a nasty bug. So we don't


let you do that. You have to wrap it in


a proc. Um if you do want to reference


the same unfrozen thing that's fine.


Just wrap that reference in a proc point


to a constant.


Um otherwise these are optional if you


explicitly use a type that is nilable.


Now I've used the nillable type


constructor here to make a type that is


essentially a union of nil and string


but you could use any type where it's


triple equals with nil pass to it


returns


truthy and this is kind of intentionally


ugly the nillable thing


um because using nillable types is


something that you want to avoid mostly


like you sometimes you have to do it but


at least it kind of like stands out to


be at least that part is like kind of


really ugly. One thing you can do you


can define the method string question


mark and point it to nilable string if


you want. Um that looks really pretty


but I almost don't want to make nillable


stuff look


pretty. So um okay so let's let's run


through the rest of this. Uh you can


also using the keyword arguments reader


and writer you can you can set them to


public, private, protected um or false


if you don't want the readers on the


writers. Um I switched to strruct here


instead of data because you can't have a


writer on a a data object. Um that's not


a valid thing. Um so so literal struts


literal data objects by default they


have they both have readers and by


default strrus have writers as well.


Um, let me just turn this on so I can


keep track of my time. So you can also


pass a block and that block will be


yielded the argument so that it can


basically coersse that argument into


your type. So say you want to call 2s on


something that you want to require to be


a string. You could do it like this or


you could even do it in the nice clean


way with um proc to uh proc to proc


sorry symbol to


proc.


Um so literal


properties that's like good for value


objects properties let you bring this


interface into other classes. So let's


say you want to inherit you need to


inherit from flex html you want to have


properties you can just extend literal


properties and then you get to do the


same thing. So this is my uh my button


component. I'm defining size to be a


union of small, medium, large. Variant


union of primary, secondary. Um and then


just setting up my props. Um and you'll


notice also I've used the proper class


because this is a this is an HTML


component. I want to pass in a CSS


class, but class is a reserved keyword.


So how does this work? The the code that


it generates will actually knows about


reserved keywords and it will use


binding.local local variable get in this


case in your in the initializer that it


generates for you and then you can just


reference it as at class and that's all


that's always valid. So you don't have


to worry about I've seen people like


take star options and then grab the


class out of that or like various hacks


or like class names. I don't like that


stuff. So um we just support it. There's


also this other tool literal enums and


these are constant enumerations. So um


there's like there's like a few kinds of


things that you can describe and like


name. One is like when you make a class


you kind of saying um like a regular


class you're kind of saying that there


can be almost like infinite instances of


this thing. With a constant


enumeration you might want to have an


instance but you just want to have like


a limited number of


instances. So a good example is like


status. Okay. So I have like a post


status or something. Um, and maybe I'm


saving this to the database and I'm


precious about my database storage. So I


want to I want to store like tiny little


integers. Um, so I'm going to make this


mapping from a constant name which is


going to be an instance of the status


class to an integer. And that's actually


constrained by the the um the fact that


we're inheriting from literal enum with


integer in parenthesis. So it's going to


make sure that those are always


integers.


Um, and what this gets you is like it's


you're defining the constant like status


draft, status pending review, status


published. And they are instances of the


status class. So you can define methods


in there. Um, they have some default


methods. So if I grab like status draft


and ask it for its value, I get back


zero. Um, oh


uh I can also send it predicates. I can


ask it is it draft? Is it published? Um,


and it will it won't raise. it will


return true or false um just based on


the names. Um and I can also coersse uh


either the um the value or the name as a


symbol. Um, and this is there's a bunch


of other things in in literal enum that


you can do like you can actually have


properties on your enums which is useful


for like I don't know list of countries


or something you want to store the name


the key the uh the code um whatever and


you can define methods on the class or


even on instances of the class by


passing a block and then defining


methods I don't have an example


um so with this status class remember


that we can um use


coercions


uh with um by passing them as a block.


So the the status enum actually responds


to two proc um and it will basically use


its coerc method. So you can make


interfaces like this post when you


create a new one you can pass in a


symbol published and it will actually


resolve via the coercion ampersand


status into um status published the the


constant


instance and actually um these classes


just going back um the class and the


instances are always frozen so they can


never change once you've created


them. Um, finally, values and


decorators. So, you could like sometimes


you you end up creating a like a data uh


a data object that just has one value in


it like it's an email address or user ID


or something like that. And for that,


there's like an easier way to do it. You


can kind of do it on one line. Um, I'm


just creating a uh like user ID here. um


capital U uh is a class and I can create


new instances of it and it just wraps


the string. Um what's nice about this is


you could actually define um your user


ID as this literal value put in whatever


your um validation is there and then


once you have one you know that it's


safe or you know that it's frozen


um and you can always get its value back


out. You can also define it like this


and you can say I want to delegate the


this set of methods to the underlying


object. And that's useful because it


means that you can kind of constrain the


interface. You're not saying like every


single um every single method that's


available on string should be available


on user ID. Um you can like specify


um exactly what you want to share.


Um if you do want to just decorate,


there's also um literal decorator which


you can use in the same way. Um and this


will just essentially use method missing


to just pass everything down. Um


speaking of method missing, let's talk


about performance. Uh it's a question I


often get about this. Um it's like we're


doing loads of extra work. Is this going


to be slow? Um it's not very slow. So


because because we use code generation,


we're not doing like um much at runtime.


Um so when when you when you use the


prop methods, you're actually defining


the initializer um with code generation.


So it's essentially as if you had


written it yourself. Um it's just doing


type checking and raising errors if if


there's something wrong. All of the


types that literal have do essentially


zero runtime allocations. It's possible


that for to generate a new inline cache,


they will sometimes allocate. Um I think


there the types that call a method on


the object being passed in will will


allocate if you um if you call them with


one thing and then you call them with a


different kind of thing. I think there's


a there's like an inline cache that gets


created but generally they don't do any


runtime


allocations. Um the types also highly


optimized. So to give an example of this


um let's look at the union type. So the


union type is usually used maybe


something like this. In this case um


it's usually just a small set of things.


If we what it's going to do is it's


going to call triple equals on string


with the value. And if that returns true


um it's going to early come out because


it knows like it's or it doesn't need to


check symbol or array. Um if it's false


it will need to check all of types. But


let's say that you passed in you created


a union of um primitives. So things like


strings, integers, um I can't remember


the full list symbols definitely. Um so


it turns out that primitives triple


equals is equivalent to their hash


equality, right? Because they're


essentially valuebased unit types. So we


can when you create a union of


primitives or if there are primitives in


there the primitives will get put into a


set. Um so you could have a union of


like a million possible tags that are


just simple primitives, strings or


whatever and then you could check in 01


time.


Um it would it would it would be


incredibly fast. It wouldn't make any


difference. Um the other thing is types


are flattened when possible. So, um,


this example of, um, we've got like


three unions and then we put all three


unions into another union. Um, this


isn't going D isn't actually going to be


a union of three unions. It's going to


be a union of nine primitives. Um,


because it's going to automatically be


like, okay, I'm going to try and flatten


this. Um, and these also all happen like


typically all of these types are being


allocated at boot time. So, they happen


once, you use the same thing again and


again. it's frozen. Um, if you do eager


loading, it's going to be copy on write


shared between all of your processes.


So, they're not going to take up much


memory


either. Um, so what's next in literal?


Uh, collection objects. So, let's look


at um an example. We've got a group that


has members, and we're checking that


when you pass in members, it's an array


of users.


But we want to add this method to add a


new member. So what happens here is


we're we check that when we get the


array at the object boundary, it's


definitely an array of users, but then


we're able to add random things like we


could call add member with an integer


and it would be fine. It wouldn't


complain. Um, so you could obviously


like do your own little check. This is a


short one that you can do. The error


messages from these are not very nice.


Um but this again because pattern


matching uses triple equals we'll call


triple equals on user um and check and


that that works but um I don't think


that group should be responsible for


this actually think that that's um the


array itself should be responsible um


just like looking at this from a a kind


of runtime perspective and so we're


going to build u or we're in the process


of building a literal array uh type that


is parameterized. So you could create


literal arrays as objects. Um you create


them with the type and with the values


and they ensure that you can't push


incorrect values into


them.


Um does that make sense so


far?


Okay. So collection objects we're going


to build we're partway through building


array. Um and actually partway into


tupil but very not very far. Um, we're


going to build hash and set as well. Uh,


and these will basically like work like


their counterparts in Ruby. Mostly every


method. The reason they're taking so


long is like these these objects just


have so many methods and like


implementing them in a way where you


can't corrupt the types is is quite a


challenge. Um, that gets us on to oh


yeah, let's take a quick detour. So I I


think tomorrow Stephen is going to talk


about this idea of like um an accordion


of complexity which is like building


interfaces that kind of flex and scale


with with you and like with your use


case and and how far you want to go. So


um I just want to show like an example


of this. So if you have you have this


group takes property of members. The


simplest thing you can just say that


this should be an array, right? I don't


need to think about generics. I don't


need to think about anything else. This


is going to give me a lot of safety,


right? It's going to tell me it's not


nil, it's not string, it's an array. The


next layer is like I want to check uh


that that it's a an array of users um


initialization. And then like another


layer is then I also want to make sure


that you can't push invalid things into


it. Um and the next layer would be like


it doesn't make sense to have a group of


one PE person or zero people. Um, so


let's make sure that the the members


must be two or


more. And next layer up is like let's


actually create like let's name that


concept of group members um so that it


can be referenced in other places or at


least so that we know what it


means.


Um getting back to collections let's


talk about variance for a second. Um,


so let's imagine we have a class fruit


and we also have these three classes


that subclass fruit. Apple, banana,


orange. This is the simplest example I


can come up with. Um, we make a basket


of fruit. Uh, we pass in a bunch of


fruit and we make a basket of


apples. And then we want to concat these


together. Right? So in the first


example, we we we have a basket of


apples and we want to shove into it our


basket of fruit. That is


invalid. In the in the second example,


uh we have a basket of fruit and we want


to concat into it a basket of apples.


That is valid. That shouldn't error. But


we don't want to have to check every


time


um you know, every time I I shove my


basket of apples into my basket of


fruit. Uh we want to basically take a


shortcut. If we can compare the types


apple and fruit to each other, we won't


have to check all of the items. So the


other thing that we're doing uh right


now is in order to support these


collection objects is make it so that


you can compare these types to each


other. And actually like you can compare


all sorts of things like you can say


like the it knows that the class proc um


is a subtype of the interface type call,


right? because it it it's in the


interface type call knows in order to


determine if I am a super type of a


class, I need to look at that class's


instance methods. So there's all sorts


of ways that we can like take shortcuts


and and have performance better


performance when working with these


things. Um and ultimately it means that


doing one quick operation just comparing


the types themselves rather than the


items um we can know that a certain kind


of concatenation or operation on an


array is


safe. Uh the other thing that we're


adding um is cross property validation.


So this is the idea that you can


stipulate


um you can make stipulations about like


um multiple properties kind of working


together. So um sometimes the the


validness of one attribute is actually


determined by another attribute. And so


the way that we're thinking of doing


this, this is still kind of


uh open to suggestions, um is that you


can make these stipulations and you pass


a block and we just look at what you've


named the parameters on that block and


then we generate the code that's able to


pass the correct um attribute into those


blocks. Um so it's the ordering doesn't


matter. It's just that you have named


the parameters correctly um and that


they match up with the names of the


properties. Um so


here like we want to know that the name


is between the lengths. We want to know


that the min is less than max. We can do


that. Uh and finally result objects. Um


I'm not going to go too into detail on


this but just this the idea of having a


result monad in literal. I know there's


like dry monads and stuff. Um, but I


think that we can do a better like job


of integrating that with literal type


system. Um, so I'm going to look into


doing that. Uh, another idea we had was


that actually literal should be able to


generate LLM schemas. Um, LLM scheas are


uh, basically a way of telling LLMs like


this is a a kind of shape of an object


that I want you to generate. And so um,


like I don't know, I'm looking for


recipes. they look like this. Can you go


to this website and find me recipes? And


then it will do it and it will give you


back JSON that matches that schema. Um,


and we should be able to generate these


and then validate these as


well. So,


um, let's just, um, kind of look back


over what we've


learned. Ruby has types and most Ruby


objects are types. I think that like we


might disagree


on whether that's true or not, but


um but whatever I'm calling types, it


definitely does have right and you're


definitely using them.


Uh we looked at literal types, we looked


at literal strruct and data um which are


mutable, immutable uh data objects. We


looked at little properties that let you


do bring the same interface into your


own objects. Um, enums let you have


constant enumerations of um of objects.


Uh, and value and and decorators let you


wrap a single value like a user ID. And


the advantage there, I probably should


have talked about this is that you can


make an interface that says I need a


user ID. Like I I don't want any string,


you need to give me a user ID.


Um, for me, having used this for for a


number of years now, um, it feels like


the sort of Goldilock solution, right?


Um, complete type safety in Ruby just


for me has been unattainable. And I've


tried a bunch of different tools. Um,


but also just being like fully dynamic,


everything's fine, don't check anything.


At the end of the day, you have to check


stuff. And so it's a case of like am I


checking stuff by writing loads of code


out manually or by using this um like


composing these beautiful types together


um in ways that I can like give them


nice names and pass them around and call


them. Um for me I much prefer to to


write less code. Um so the benefits for


me are um less typing like literally um


I don't have to type as much on my


keyboard. Um this concept of a property


has been named


So what was before maybe like six


different things. Let's see. You've got


your initializer that takes parameters.


It assigns instance variables. It checks


that they're valid. Then maybe you've


got an at a reader separately in


different part of the code. And then


maybe you've overridden the atter writer


to have a to check something or to


coersse something. And that code has to


stay in sync with your initializer.


you can end up writing a lot of code to


try to do do this. Um, but having this


concept of a property as being like this


one thing. Um, for me it just really


simplifies that idea. Um, concept of a


type, you're already using them, you're


already using the concept. I'm just


naming it a type. Um, a duck is a duck.


Um, local documentation, right? So if I


am in any class that uses this way of of


writing Ruby, I can just scroll to the


top and I know what everything is. Like


it just tells me um and if I'm using


Ruby LSP and I see like it has an array


of users, I can just command click on


user and I jump to that class, right? I


don't have to be like, oh, what is a


user in this case? Is it is it a because


it could be JSON? It could be anything.


Um, local documentation also is better


for LLMs, right? Even if you have an


like an agentic LLM tool that's able to


go and look at like look up other files,


it's not going to be able to look up all


your files and find all of the different


places where you've kind of defined what


this what what this class is meant to do


which are outside of the class, right?


We want the the conventions to be owned


by the class and enforced by the class


and visible from the same file that that


class is defined in, not defined in all


of your code all over the place. Or at


least that's that's what I want. Um, and


LLMs do a really good job when they're


able to have that context immediately.


Um, we get to make invalid states


impossible to some extent. I should put


a star here. Like it is not a 100% type-


safe system. It's it's not even


Typescript level, but but adding these


validations at the boundaries of objects


gets you so far um in terms of like you


run your tests and if there's something


wrong, they're like they're going to


pick it up straight away. Um I didn't


add this, but actually like great error


messages is a I would consider to be a


feature. So our types are very um


careful how they generate error


messages. So if you have a type that's


like array of string and you give it an


array but like the third item is wrong,


it won't just tell you that the whole


thing was wrong. It will tell you the


third item was this. We expected it to


be one of these. Um and the errors are


really nice. Um and the great thing


about doing it at runtime is it's always


compatible with existing code. Um you


can use this and get value from it in 5


minutes. And um you can keep using meta


programming, which for me is what makes


Ruby so great. Like I can move so fast


in Ruby compared to TypeScript when I'm


building certain kinds of things. Um I


wouldn't give that up to have static


typing. Um I already said your existing


tests are more useful. Um, but even if


something gets past development, gets


past your tests, it makes it into


production and some crazy bug in Pummer


and Rails, um, means that one user is


treated as a different user and so they


don't have email addresses and they end


up nil. You're not going to end up


deleting 4,000 emails from Zendesk.


Um,


so I would say that Ruby has literally


always had types. Um, it's just that


we've been thinking about types in a too


narrow a sense like as being like static


types. Um, Ruby has runtime types and


uh, and it has a great interface for


working with them and for building


them. And that is the end of my talk. I


think I got it in 45.


Let me just


[Applause]


uh All right. Any questions?


Really?


Hi, thank you for the talk. Very nice


presentation. Could you tell me the


difference between literal fun and dry


types? for example. Yeah.


Um, literal uses the triple equals


interface um where dry doesn't. So,


uh, dry types are like a specific thing.


Um, they they also cover more than um


more than type checking. Like a dry type


can actually actually knows um how it


can be coerced and what its default


value is. And for me, that's like the


wrong place to put that information. for


me that belongs with the property the


idea of a property and the type should


be focused entirely on um basically it


has two functions. Um one is determining


um whether an object is described by it


and then the other one which is optional


is determining whether another type is a


subtype of


it. So it's just like being able to do


that means that you can use like all of


the objects in Ruby. they're already


types. Like I can just use a string as a


type or the class string as a type. I


don't need to have this extra thing.


Any other questions?


Thank you for presentation. Um I had a


question. this literal data u does it


throw an error when you when you pass


the wrong prop? Yes, I should have said


that. Yes, that's literally what it


does. Yeah. And so in production is the


same. Yeah. So you have just exception


and you have to deal with it. Yeah. So


we


um when I originally built this gem, I


made it with the ability to disable type


checking. So you could run it in


production. you could turn type checking


off. And the the reason that I did that


initially was like I thought it was


going to be a performance issue. Um but


I ran it in production with type


checking on and it was unnoticeable. Um


and


actually what you don't want to do in


production even though it kind of seems


um unintuitive is just keep going when


something is wrong. Um because it's


better to raise an error than to like


keep deleting 40,000 4,000 emails. Um


so what I found is it means that you're


more likely to have errors like surface


quickly, but then you can deal with them


quickly. You've got a you've got an


error that tells you exactly what went


wrong. It tells you like what it got,


what it should have been, and you can


fix that. Um and actually like when when


we deployed this at clear scope um they


our error our production error rate went


way down because we were just like


running this locally running it in our


test environments just brought it way


down. So even though there are more


opportunities for it to raise it didn't


raise more in


production. Thank you.


Hi, thank you for the talk. I just like


how people have the same questions as I


do, but I have a third one. So, okay.


Uh, yeah, when you're defining a public


method, you usually want to specify the


types of the attributes along with the


definition. So uh as far as I understood


the way you will do this is by adding a


pattern matching string at the first


line right? Yeah you could do that


um


where I've explored like different


interfaces that you could use at runtime


to do like type checking on the me at


the method layer. Um and I haven't found


any that I really love. Um,


and I've like it's at the point where I


you get so much value from just checking


at object initialization the object


boundaries. Um, that for the most part


that works very very well. Um I think


yeah if you're doing if you're if you


want to check a type on a method um you


either want to have that like create an


object like if you have like a service


object or something um or yeah do your


own do your own types using


um the the pattern matching. Thank you.


May I ask another one? Uh yes do you


think that this gem is a good uh fit to


write a validation library? I mean when


the user passes you some parameters and


you return to them the list of errors.


Yeah.


Um I I would like I think so. Um right


now it's not great for that because you


just try to create it and it will raise.


Um I think it would be useful if you


could instead of trying to create it you


could like call a like validate instead


of new. Um and you could it would like


do like a soft run. So it would like


check it would it would check against


its schema um the the parameters and


then um it could give you back just like


an array of errors or something rather


than raising an exception because you


don't want to raise as like part of


everyday um everyday code. So yeah um I


think it would be a good basis but it's


not quite there yet.


So you said that Ruby people don't like


types. Um I think what Ruby people don't


like is Ferose code. Yeah. Which usually


is a result of you know describing the


types. I think in RBS you can define the


types outside of your actual class.


Right. You can. Yeah. Have you


experimented with this idea as well? Uh


I haven't. No. Um I it's it's already


the case that switching from like if you


if you just switch from using um a


standard initializer where you're taking


parameters and you're assigning them to


instance variables, you're already going


to be writing fewer lines of code just


by switching to to use literal. So I


don't see it as being like having like a


deficit there in terms of lines of code.


Um, I personally really value being able


to scroll to the top of any class and


immediately see what everything is and


click to jump to them and stuff like


that. Um, yeah, I haven't explored like


doing it in a in a separate file.


All right, the last question and the


rest of them. Joel, are you joining us


after the after party? Yeah. Okay, there


will be plenty of time to discuss


something completely different. Do you


think because you started with the


problem that um it's a lot of code like


yeah twice as much to describe the


types. Do you think the code in


production could be observed by some


tooling like let's say cover band and


that observation could be used to


generate the types. So we would not


write them assume user is user and then


something we would observe that user is


actually user and generate the types for


you like separately maybe would this


just be like part of trying to migrate


to this rather than a long-term thing


because I think the issue with that is


um well it's it's a few things. So one


is like let's say that always got


um like I don't know your size is always


like small, medium or large, right? it


sees that in production, but does it


know that it's a union of small, medium,


or large, or will it have to assume that


it's a string? Um, like that would be


quite difficult to figure out. Um,


also, if it ra like it it's only going


to work one time. If it raises in


production if it's not the types it


expected, um, then it's never going to


have any production data to be able to


to inform what types they should be. So


um unless you're thinking of it as like


a kind of picks up anomalies like


usually this is a string but but then I


wouldn't want to have a system like that


one person it was something else so and


then you need to review it right like


you would it would give you pull request


saying you want this and then you say no


that nil was actually not expected would


be a bit too late I I wouldn't want to


have that um raising errors in


production personally because sometimes


you have anonymous like it is valid for


it to be this thing but it's unusual. Um


so I I don't know if I would want that


myself. Um I think someone has been I


think Marco has been um doing some stuff


on like analyzing apps in production and


then generating like documentation


that's that's I think would be useful.


Um I just wouldn't want to have like


that actually making exceptions raise in


production on the basis of of that kind


of thing. personally more like generate


the types like RBS types.


Yeah, but like you could you could do it


you could have it generate the types but


then they're not very useful if you


can't exercise them. So if you had RBS


types, even if you had a a static type


analysis tool that worked, um which it


wouldn't work with any meta programming,


um you would still like what you'd still


end up in a situation where you had to


go and like adjust the types as you as


you as they raised issues. Um so I don't


I don't know if I would recommend like


that even as a migration path. like the


migration path that I would take would


be um take some like simple objects or


even better still find places where you


should have had objects and you didn't


extract small objects


um and uh and make new objects and like


you can use you can use the type never I


think um that will make it always raise


like never never basically just returns


false always um and that will tell you


like this attribute was wrong and and I


got this instead and then it's quite


easy to to track down that attribute and


correct it. Thank you very much.


Right. Big pause for Joel, please. Thank


you.


[Applause]