Type's can be fun and useful, and i'd love to see them incorporated into ruby in a tasteful way. i don't want it to become a new thing developers are forced to do, but there is a lot of utility from making them more available.
1. Flowsensitivity: It's a sure thing that in a dynamic language people use coding conventions that fit naturally to the runtime-checked nature of those types. That makes flow-sensitive typing really important.
2. Duck typing: dynamic languages and certainly ruby codebases I knew often use ducktyping. That works really well in something like typescript, including really simple features such as type-intersections and unions, but those features aren't present in C#.
3. Proof by survival: typescript is empirically a huge success. They're doing something right when it comes to retrospectively bolting on static types in a dynamic language. Almost certainly there are more things than I can think of off the top of my head.
Even though I prefer C# to typescript or ruby _personally_ for most tasks, I don't think it's perfect, nor is it likely a good crib-sheet for historically dynamic languages looking to add a bit of static typing - at least, IMHO.
Bit of a tangent, but there was a talk by anders hejlsberg as to why they're porting the TS compiler to Go (and implicitly not C#) - https://www.youtube.com/watch?v=10qowKUW82U - I think it's worth recognizing the kind of stuff that goes into these choices that's inevitably not obvious at first glance. It's not about the "best" lanugage in a vacuum, it's a about the best tool for _your_ job and _your_ team.
For the best experience you may want to add `<WarningsAsErrors>nullable</WarningsAsErrors>` to .csproj file.
I think it's pretty usuable now, but there is scarring. The solution would have been much nicer had it been around from day one; especially surrounding generics and constraints.
It's not _entirely_ sound, nor can it warn about most mistakes when those are in the "here-be-dragons" annotations in generic code.
The flow sensitive bit is quite nice, but not as powerful as in e.g. typescript, and sometimes the differences hurt.
It's got weird gotcha interactions with value-types, for instance but likely not limited to interaction with generics that aren't constrained to struct but _do_ allow nullable usage for ref types.
Support in reflection is present, but it's not a "real" type, and so everything works differently, and hence you'll see that code leveraging reflection that needs to deal with this kind of stuff tends to have special considerations for ref type vs. value-type nullabilty, and it often leaks out into API consumers too - not sure if that's just a practical limitation or a fundamental one, but it's very common anyhow.
There wasn't last I looked code that allowed runtime checking for incorrect nulls in non-nullable marked fields, which is particularly annoying if there's even an iota of not-yet annoted or incorrectly annotated code, including e.g. stuff like deserialization.
Related features like TS Partial<> are missing, and that means that expressing concepts like POCOs that are in the process of being initialized but aren't yet is a real pain; most code that does that in the wild is not typesafe.
Still, if you engage constructively and are willing to massage your patterns and habbits you can surely get like 99% type-checkable code, and that's still a really good help.
If it's an object, it's as simple as having a static method on a type, like FromA(A value) and then have that static method call the constructor internally after it has assembled the needed state. That's how you'd do it in Rust anyway. There will be a warning (or an error if you elevate those) if a constructor exits not having initialized all fields or properties. Without constructor, you can mark properties as 'required' to prohibit object construction without assignment to them with object initializer syntax too.
Typescript's partial can however do more than that - required means you can practically express a type that cannot be instantiated partially (without absurd amounts of boilerplate anyhow), but if you do, you can't _also_ express that same type but partially initialized. There are lots of really boring everyday cases where partial initialization is very practical. Any code that collects various bits of required input but has the ability to set aside and express the intermediate state of that collection of data while it's being collected or in the event that you fail to complete wants something like partial.
E.g. if you're using the most common C# web platform, asp.net core, to map inputs into a typed object, you now are forced to either expression semantically required but not type-system required via some other path. Or, if you use C# required, you must choose between unsafe code that nevertheless allows access to objects that never had those properties initialized, or safe code but then you can't access any of the rest of the input either, which is annoying for error handling.
typescript's type system could on the other hand express the notion that all or even just some of those properties are missing; it's even pretty easy to express the notion of a mapped type wherein all of the _values_ are replaces by strings - or, say, by a result type. And flow-sensitive type analysis means that sometimes you don't even need any kind of extra type checks to "convert" from such a partial type into the fully initialized flavor; that's implicitly deduced simply because once all properties are statically known to be non-null, well, at that point in the code the object _is_ of the fully initialized type.
So yeah, C#'s nullability story is pretty decent really, but that doesn't mean it's perfect either. I think it's important to mention stuff like Partial because sometimes features like this are looked at without considering the context. Most of these features sound neat in isolation, but are also quite useless in isolation. The real value is in how it allows you to express and change programs whilst simultaneously avoiding programmer error. Having a bit of unsafe code here and there isn't the end of the world, nor is a bit of boilerplate. But if your language requires tons of it all over the place, well, then you're more likely to make stupid mistakes and less likely to have the compiler catch them. So how we deal with the intentional inflexibility of non-nullable reference types matters, at least, IMHO.
Also, this isn't intended to imply that typescript is "better". That has even more common holes that are also unfixable given where it came from and the essential nature of so much interop with type-unsafe JS, and a bunch of other challenges. But in order to mitigate those challenges TS implemented various features, and then we're able to talk about what those feature bring to the table and conversely how their absence affects other languages. Nor is "MOAR FEATURE" a free lunch; I'm sure anybody that's played with almost any language with heavy generics has experienced how complicated it can get. IIRC didn't somebody implement DOOM in the TS type system? I mean, when your error messages are literally demonic, understanding the code may take a while ;-).
Another commenter suggested another language like crystal, and that might actually be what it really needs, a ruby-like alternative.
interesting that you've called out C# as the specific example of a way to approach typing. c# did nothing new with their typing system, instead copied from java and others https://en.wikipedia.org/wiki/Comparison_of_C_Sharp_and_Java...
There's 6 open bugs and 4 closed ones. This seems like either it's throwing shade or they didn't bother lodging bug reports upstream.
Readme:
>Note: TypedRuby is not currently under active development.
I think "brief check, move on" is reasonable, particularly if it doesn't appear to already be at several-year-stable quality.
Sometimes I long for the days before type theory took over programming language research.
There's not a ton of value to be had in something like Typescript or Python types.
Correctness is more important (in general and as a benefit of type checking) than optimization. Doing the wrong thing fast is easy, but not valuable.
Also what's good for enterprise isn't good for everyone.
I get that orgs probably like TS so that newbie devs don't do crazy things in the code, and for more editor hand-holding. But it's not valuable for everyone, if it was actually better than everyone would be using it, not just some people.
...saying this as someone who benefits from it but also rarely uses sub-typing ("poodle" type sub to "dog" type sub to "animal" type) or any sort of the other benefits that are commonly associated with typing. for me the benefit is code checking and code navigation of unfamiliar code bases
duck typing is what ruby does without sorbet or rbs, it portrays nothing about how the code bits interact at the boundary. if a "dog" is passed in but a "cat" is expected, things will work just fine until runtime when the cat is asked to bark. (saying as someone who is a big fan of ruby overall)
It is developer responsibility to ensure that you will not receive cat during designing stage of your classes. And inheritance is bad, this is also sharp knife. But annotating everywhere
sig { params(cat: Cat) }
does not improve your design, it just makes noisy and clumsy. I would think that if your code need type annotations, it smells like bad design and should be considered for refactoring.The Sorbet syntax is pretty bad, though. And things go from bad to worse as soon as you get a bit tricky with composition or want to do something like verify the type of an association.
I haven't tried inline RBS comments yet, but the DSL does look more pleasing.
The team in general had mixed views. Some hated it, some liked it, most developers just don't really care–although now the Sorbet advocates are claiming it helps the AI, so now there is that leaning on the Sorbet lever as well.
There are statically typed languages: go, java, rust, even c++. Why one would try to make ruby "look like" statically typed language? It violates the idea of ruby, which dynamically typed language. You can create amazing tools if you think in dynamic way (duck-typing, meta-programming, runtime definitions). There are messages that you send to objects not statically typed bits and procedures (which the way of thinking for statically typed languages and procedural programming). You even have in your toolbox methods like (to_int, to_str, to_ary), that already do type-check strictly.
When you add types to ruby what are your benefits? It becomes faster language? No, it becomes even slower. It becomes more readable? Alas no. It becomes type safe? Yet no. It becomes more verbose with this type annotation everywhere making it look like nightmare. Why should one use hammer with self-tapping screw? Use screwdriver for that kind of activity.
Ruby is a language that grants you lots of freedom.
Nobody can argue that. With freedom we can do anything, but anyone wondering why in first place?What about wisdom and pragmatic approach? If you ever need types why not choose typed language for your case, but instead re-implementing something and wasting so much time for work that has no real use. Why not use c-bindings for typechecks, this feature is already available in ruby *rb_obj_is_kind_of(value, rb_cString);* and has no cost. Is it wise? I don't think so, I would be shocked if I saw such code in production.
We care so much about environment and climate changes, but think it is ok to waste resources for such activity. It is not research task. People really advocate to use in real-world applications such thing as sorbet. DHH even removed typescript from his frontend libraries, and this types is like kindergarten for some homemade rack/hanami/dryrb application that will server 2 people in production (creator of this stuff and random visitor). Maybe someone could tell me why we need such thing in ruby ecosystem like rbs/sorbet library. I'm really wondering. Not because ruby grants us freedom and we can do that. That's not an answer. We can do enormous count of absurd things. But the question is why?
But, why not choose a proprer statically typed language? Because you've inherited a mastodon of a legacy app that started simple, then matured with thick layers of features. How can you wrangle this? Oh just rewrite it in Rust. Or you could seek an iterative approach and introduce tools to help you. Like bolted-on types.
this is part of the appeal of the "header file approach" a la RBS, as mentioned in the article https://blog.jez.io/history-of-sorbet-syntax/#the-header-fil...
People said the exact same things when Typescript was first being released. I think history has proven all those people decisively wrong. I think you'd need a compelling reason that Ruby has some je ne sais quoi that Javascript doesn't in order to support your point.
We should invent new programming language typed_ruby and give him separate life, and then history will prove us whether it should have been implemented in the first place. But not try to push it into stdlib and persuade everyone else that it is the right way to go.
sig ->(_: MyData) { }
def self.example(my_data)
...
end
Obviously this opens up a potential can of worms of a dynamic static type system, but it looks sufficiently close enough to just ruby. My opinion is that sorbet doesn't lean into the weirdness of ruby enough, so while it has the potential to be an amazingly productive tool, this is the same community that (mostly) embraces multiple ways of doing things for aesthetic purposes. For example you could get the default values of the lambda above to determine the types of the args by calling the lambda with dummy values and capturing via binding.Personally having written ruby/rails/c#/etc and having been on a dev productivity team myself, I say: lean into the weird shit and make a dsl for this since that's what it wants to be anyways. People will always complain, especially with ruby/rails.
dismalaf•7h ago
I get that a lack of types is a problem for massive organizations but turning dynamic languages into typed languages is a time sink for solo developers and small organizations for zero performance benefit. If I wanted a typed language to build web apps with I'd use Java or something.
Hopefully Matz sticks to his guns and never allows type annotations at the language and VM level.
kodablah•7h ago
It's not be more like those, it's be more like helpful, author-friendly programming which is very much Ruby's ethos.
Every time I think about ripping out all of the RBS sig files in my project because I'm tired of maintaining them (I can't use Sorbet for a few reasons), Steep catches a `nil` error ahead of time. Sure we can all say "why didn't you have test coverage?" but ideally you want all help you can get.
PaulHoule•6h ago
jimbokun•4h ago
Well types are a form of test performed by the compiler.
jaredsohn•2h ago
This maybe already exists, but it would be nice if RBS or Sorbet had a command you could run that checks that all methods have types and tries to 'fix' anything missing via help from an LLM. You'd still be able to review the changes before committing it, just like with lint autofixing. Also you'd need to set up an LLM API key and be comfortable sharing your code with it.
jbverschoor•5h ago
In Objective-C, you also pass messages to an object, and it could be anything you want. But it would output warnings that an object/class did not respond to a certain message. You could ignore it, but it would always result in a runtime error obviously.
Swift is a much nicer language in terms of typing, and I have started replacing some smaller Ruby scripts with Swift. Mostly out of performance needs though. (Single file swift scripts)The lack of proper typechecking is one of the main reasons I would not use Ruby, especially in larger teams.
Unittests are fun and all, but they serve a different purpose.
dismalaf•4h ago
Cool. I'm using Ruby for a startup because it's 10x more productive for building a webapp versus a compiled language and because I'm solo.
And when I want typing I use something like C++ because I don't want to lose productivity without a major performance boost.
Lio•3h ago
I also wonder if we’ll eventually be able to pass type information to the JIT. If that helps ruby grow I’m all for it.
dismalaf•2h ago
It's not like Ruby doesn't have types and the JIT can't determine them. It just happens at runtime, literally the defining feature of dynamic languages.
Lio•2h ago
__float•17m ago
Why? Just because they _exist_ doesn't mean you'd need to use them.