Types will be part of Ruby 3 stdlib source(twitter.com) |
Types will be part of Ruby 3 stdlib source(twitter.com) |
The earliest possible date (and somewhere I read it's a probable one, but I can't find it right now) is Christmas 2020.
I worked on a summer project to add type annotations to Ruby. Didn't get very far since I ran into some challenges with the internals of the parser and the parser library, Ripper. I'm extremely interested in seeing how the Ruby team designs the type system. It'll be gradual, of course, but also it'll be interesting what adaptations they'll have to make to accommodate existing code. JavaScript relied on a lot of stringly typed code, so TypeScript added string literal types. Perhaps Ruby's dynamic, block oriented style could lead to some interesting decisions in the type system.
Not to mention, the types will most likely be reified as per Ruby's philosophy.
Super excited for this. Between the JIT and types, Ruby could definitely see a renaissance in the near future.
And +1 on the Ruby renaissance! Super excited about all the exciting things that are currently being built!
Keep in mind, Ruby development is headed towards a goal that the dev team has called "3x3" as in Ruby 3 aims to be three times faster than current Ruby implementation.
I was disappointed to find out that adding more types in Perl6 actually slows down performance.
I wonder what the differences are that adding types in one language speeds it up, while adding types in another language slows it down.
Quite a few large companies have found themselves in this situation: Very large codebases in a programming language without types stop being fast to develop in. Then you get to either rewrite everything, with the well documented risks, or start doing all kinds of other things to make programming safer, like banning certain parts of the language, until eventually dedicating a team to improve the language is the most cost effective way to go.
In this case, I am also pretty certain that the interaction with data started having informal types a while ago too.
What I find really interesting here is that what starts as a library to help a single company handle the subset of Ruby they were using in the first place now aims to be good enough for general purpose Ruby outside of said company. It's one thing to have problems with an experimental, home-made thing, and just get support via slack, but adding this to the language has a far higher barrier. This is also probably the reason it's not OSS yet: The code that is enough for production use in Stripe's approach to Ruby might not be the greatest in a random codebase with different opinions on how many dynamic methods you want to have.
So it's not that a team decides to add types to Ruby instead of just picking a language that already has the types: It's solving a private problem and, a while later, realize that accidentally the solution is very close to being good enough for the language.
Speed is not the first and foremost benefit of types. Type checking is (and other stuff that comes with that, like better completions, self-documenting code, etc).
One of the reasons why Sorbet does both runtime checking[1] more than just static checking is so that we can know that signatures are accurate, even when a typed method is called from untyped code.
If the signatures are accurate, a future project could take advantage of method's signatures to make decisions about how the code should actually be run. If the signatures lie, then any runtime optimization made using the types would only be overhead, because the runtime would have to abort the optimization and fall back to just running the interpreter.
In certain basic blocks typed ints or floats can be unboxed, if they will not escape. This is what php7 made 2x faster. the stack will get much leaner. simple arithmetic ops can be inlined, using native variants. ops with typed vars cannot be overridden.
Another feature of even optional types is creating uniformity to allow JIT optimization. A great real-world example of this is Typescript or ReasonML. It's converted to JS, but still winds up faster on average. The JS JITs have multiple tiers of optimization. Changing data types and function signatures are the biggest performance killers. If you can ensure a list is always strings or numbers, then the optimizer can reach the top tier of optimization. When lots of people work together on untyped languages, there tend to be small changes in the signatures and structures that drop you out of that top optimization level. Even partial types are useful for preventing this.
Related to that is the potential for runtime type warnings. Even though the types aren't used by the JIT, it should be possible to give a warning message if the received types don't match up. That could be a huge assistance in finding where a bug is located.
now that ruby has an actual jit compiler, it could benefit from typing to optimize code further. And a gradual migration process will help people speed up parts of their code. Unless they mess it up like python where abstractions are costly.
One of the major things that has kept me using Groovy over the last 10 years was the reluctance to leave optional / gradual typing behind. Now, nearly every major dynamic language has given in and introduced types, so it seems like this idea of hybrid dynamic/typed languages is now fully mainstream. The problem of course, is they are all built on a legacy of untyped code, not to mention giant communities of people with no culture or habit of tying their code. So it's not clear to me that any amount of added language features can actually compensate for that.
I have said for awhile that "Ruby with types" would be my favorite language to work in. I recently returned to Ruby briefly and had to integrate with a poorly-documented API. I spent more time digging through third-party code trying to figure out what certain parameters were supposed to be than writing the program itself.
* Can I emit typed REST API docs out of sorbet types?
* Can I coerce HTTP params out of sorbet types?
* Can I emit ActiveRecord columns? ActiveModel validations?
* Can I emit generative tests?
You can do all of those (and whatever else you imagine) with clojure.spec in a DRY manner, i.e. types are defined once, and reused in a variety of contexts.
As a Rails dev, I would greatly value all of those, particularly because they're practical things directly related to my webdev activity. Ensuring the type safety of the codebase is great, but also implicitly exercised by an adequate test suite.
Meanwhile, I've gotten myself more and more into Clojure. Which now that other dynamic languages seems to move closer to types, seems to be in a niche in that Clojure is moving further away from types.
It'll be interesting to see what happens at both extremes and in the happy middles.
For example, Java is pretty terrible at type inference (still) and you have to annotate types almost everywhere (Java 8 had a very tepid improvement on that front.)
But languages like Haskell and Rust are very good at type inference, and you almost never actually need to specify the types.
It's still good Haskell style to always annotate the type sigs of top-level functions. Why? Because they serve as more than just hints to the compiler: they are part (and a very important part!) of the documentation. That is why they're in-line. Because A function like
zipWith:: [a] -> [b] -> (a -> b -> c) -> [c]
tells you what it does in its type signature.Java 10 and 11 introduced real type inference, at least for local variables and function parameters.
Half of the documentation will be in the header file and the other half in the implementation file and you will have to edit two files for every tiny change you make. No thanks. Types are part of the code and should be as close to the code as possible to reduce any possible source of friction while editing.
By now I think it is quite obvious that type annotations aren’t as helpful as initially expected and that a library approach seems more pragmatic and more powerful. See Clojure + Spec.
The thing is dynamically typed languages with type annotations tend to no longer feel like dynamically typed languages as the annotations and the tooling spreads and spreads and spreads. Not easy to put up boundaries.
PHP started added type hinting (aka specifying types for function arguments) in 5.0.0, back in 2004. Dartlang didn't exist until 2011, TypeScript until 2012, and Flow (I assume you mean the FB tool) didn't exist until 2014, as best I can tell.
>By now I think it is quite obvious that type annotations aren’t as helpful as initially expected
My only take away from this is that you obviously haven't used PHP's type system.
There seems to be a never ending cycle of new languages that are dynamically typed because it is easy for small codebases, which then become popular, get large codebases and then realise that static types are actually a really good idea.
Python, Dart, Ruby, JavaScript (via Typescript), etc...
“foo”.class # => StringWhy the privacy? Are programmers too dumb to understand something is a beta?
What if in the end adoption is marginal and Ruby Core's time was wasted?
Best adoption is organic, not hyped up.
The problem is that most popular dynamic languages are really quite terrible. They have atrocious runtime environments and usually quite limiting language semantics.
I agree that most popular dynamic are quite terrible. But, honestly, I think the real problem is not the particular implementations, but the whole idea of dynamic typing. At first it did make sense, but now that compiler writers have figured out "cheap" and general type inference, I don't see the point anymore.
However, I use Python on a daily basis because I have no decent alternative for the libraries I use.
Check it out. https://github.com/hernanwilkinson/LiveTyping
But Ruby used to advocate for them, and it's definitely what drew me in. I find it disappointing that we're moving away from that. More and more, it seems we’re attempting to make Ruby all things to all people. Which eventually makes it the right thing for no one.
If it's not, why not leave these solutions in gems?
Btw, I don't think static typing alone is Ruby becoming all things to all people. In recent history, it's also aliasing `Enumerable#filter` to `Enumerable#select`, numbered block arguments, a shorthand special notation for `Object#method` -- it feels like a trend of "hey these other languages do this, we should too". I'm not convinced that's always the case.
This might be misleading. That is, jump to around the 29 minute mark where he talks about the type profiler and .rbi file stuff.
also for some reason homebrew really likes to updates its index all the time (I think it got tamed in the newest version), but setting HOMEBREW_NO_AUTO_UPDATE to 1 helps a lot.
Ruby 3 static analysis will have four items:
1. Type signature format 2. Level-1 type checking tool 3. Type signature profiling / prototyping tool 4. Level-2 type checking tools
and so on. [1]: https://docs.google.com/presentation/d/1z_5JT0-MJySGn6UGrtda...
sig {params(name: String).returns(Integer)}
... why not simply: sig {name: String, returns: Integer}Classes are types by default, but you can define non-class types as well: https://sorbet.org/docs/abstract
Having said that as far as I understand, type support in Ruby 3 will not prescribe which type checker is used and what limitations exist. Some of the mentioned projects are structural and I think even Sorbet might add support for it at some point.
In a duck-typed language, type is defined by the willingness of a message receiver to receive that message. Class, inheritance, composition are all means to achieve this, but the type of an object is determined by its signature, not its ancestor chain.
Some languages are better from a static POV, and offer some auto features. Some languages are better from a dynamic POV and offer some hinting feature.
You don't want to type your code to do data exploration and analysis, but you may want to extend the original project later to something bigger and move on to types.
There is no such thing as the perfect language for everything anyway. Plus, it's very good that some languages integrates unnatural features to them, for the case where you want to go beyond their initial best case scenario. It won't be perfect, but I don't need perfect, I need programmatic.
The world of programming is vast, the pool of programmers very heterogeneous, and the constraints are super diverse.
People tend to forget this all to easily. For example most of the static type discussions for the past 10 years have taken place on a website built in a dynamic programming language, I'm talking about http://lambda-the-ultimate.org/ which afaik is built in Drupal (i.e. PHP).
You could theoretically do all of that before anyways with clever use of reflection, but this makes the compiler create all of that extra code for you from what looks like normal code.
Best use Groovy for dynamically typed scripty stuff only, and a JVM language built with static typing from the ground up for building the actual systems, such as Java, Scala, or Kotlin.
That could be the best of both worlds.
What about clojure.spec?
Clojure seems to have double downed on dynamism and runtime construct, away from static types. It seems to have made the bet that better software (less defects, cheaper to maintain and extend, more targeted to the users needs) is better achieved through:
* Simpler primitives * Immutability * Interactive development * Higher level constructs * Data driven DSLs * Generative testing * Contract specifications * Data specifications
Which are all very good ideas, but they're non traditional compared to formal static type systems and proofs.
They're used to be more drive behind these in the past, Common Lisp and Eiffel embody a lot of these ideas, but miss on others. So Clojure is like a new take trying to fit in all these ideas of interactive, dynamic, safe languages together a new.
And I just find it interesting, because it is counter current. As others have pivoted back to static types, Clojure went all in on dynamism.
Time will be the true test, and I'm looking forward from the learnings in all directions.
I also feel that CL and Racket have embraced types a lot more. Doesn't CL have a fair bit of static typing already? And with Racket, Typed Racket has pretty much pioneered the concept of gradual typing now being applied to JS, Python and Ruby.
I know Racket also explored contracts, and has a lot of great ideas. But I feel overall it's missing the: "and we dog food it all on real business use cases in production" aspect that Clojure has.
And for CL, it doesn't seem to have as much in terms of contracts, data DSLs, immutability, simpler primitives, etc. It feels more like a traditional mutable, OOP, dynamic language. It has nailed down the interactive development part though. I don't want to put it down as I'm interested to try more of CL, but overall, it just doesn't seem as active or opinionated anymore. If anything, CL seems to lack any form of opinion, and goes more for the: we just add all features of every other language. Which is a quality on its own, but not driving the discussions forward either.
- https://sorbet.run/talks/RubyKaigi2019/#/53
- https://sorbet.run/talks/RubyKaigi2019/#/55
So I don’t think that the divide will be at Rails. And more than that, I think there will be very little divide at all. Sorbet is designed to be gradual, so it works 100% fine with untyped code:
But I personally wouldn't hire someone who maintained that dynamic typing produced as good results and was reasonable for an even mid-sized project. They've either never had a long running project or they've never dealt with a big enough code base at that point, or they're simply being dishonest or lack self awareness. None of those are good signals. Not having worked on a project that goes on for long enough is fine, but having opinions on software maintenance in that case is foolish.
In this case, your example is not valid syntax, which violates this rule. Not that I personally could tell you why the parser makes a distinction here, but it's at least part of the reason :)
irb(main):010:0> foo {a: "b"}
SyntaxError: (irb):10: syntax error, unexpected ':', expecting '}'
foo {a: "b"}
^
(irb):10: syntax error, unexpected '}', expecting end-of- input
foo {a: "b"}
^
from /Users/bhuga/.rbenv/versions/2.4/bin/irb:11:in `<main>'
irb(main):011:0> foo {params(a: "b")}
NoMethodError: undefined method `foo' for main:Object
from (irb):11
from /Users/bhuga/.rbenv/versions/2.4/bin/irb:11:in `<main>'
irb(main):012:0>
The `sig` syntax has gone through multiple iterations; within the boundaries of Ruby syntax this is the best we've had. sig {params(name: String).returns(Integer)}
... why not simply: sig [String]=>[Integer]
Yes, that's just ruby - see https://github.com/s6ruby/ruby-to-michelson/blob/master/cont... for example for live running code (in secure ruby) :-) sig {params(new_value: T.nilable(Integer)).void}
... why not simply: sig Integer? # or
sig [Option.of(Integer)]=>[] # longest form in sruby
sig [Integer?]=>[] # same as Integer?It was a design decision that all type annotation arguments be named as opposed to positional. As one example why, it makes the error messages better. You can always say "You're missing a type declaration for parameter 'foo'" as opposed to "You have four positional arguments and 3 types".
We could probably still bikeshed our annotations inside the `sig { ... }`. I'm not sure we'd make constants with unicode like BigMap‹Address→Account› for generics, though, how do you even type that? :)
If not then why are you using a dynamically typed language? If you're not using it's abilities then it doesn't sound like the right tool for the job.
It's like a cost/benefit analysis where none of the benefits you're using, and the cost is the total inability to validate, refactor, and navigate your code base.
Yes, yes, and no. I do most of my work in languages that prevent the first two, but when I do have access to this kind of runtime trickery I do use it when useful.
The issue is not "Do you purposefully do those things?", but rather "Do you have a call stack where you can't guarantee it won't happen by accident?" Type checking is not relevant when you know what will happen and want the dynamic/ducktyping behaviour.
Another issue is: I'd use a different framework which doesn't use Ruby, but this was the most productive framework at the time the codebase was started, and nobody will port that many lines of code to a non-dynamic language now. So the best course of action is to validate the current code is not overly-dynamic.
Will be interesting to see how ruby handles types vs duck typing etc 10 years from now, when the new best practices have been figured out.
I guess some companies started fast with Ruby/Python and similar and instead of rewriting to static/typed languages they pushed forward features that would allow them to just continue where they left off at the expense of having a more concise problem oriented programming language that's good for solving specific problems.
One buys into eco-systems, not language features bullet point list.
And there isn't something like an universal eco-system for any kind of business case, hence multiple languages.
I'm personally tired of staring at variables trying to figure out what they're supposed to be, then having to dive into source to see how its used. C/C++/C# solved that problem, why are we still dealing with it?
Depending on your type system, a well-typed program can eg run faster, because the compiler / interpreter can elide certain runtime safety checks that would be necessary in untyped code.
If your type system is crazy enough, you can even track the runtime complexity of your program at the type level, including whether your program runs in finite time. See eg Dhall (https://dhall-lang.org/) whose type systems only allows programs running in finite time.
But honestly, if you're asking your IDE to do it, that means you're asking your IDE to do static analysis of your code - and type-checking in a lot of ways is just another static analysis technique. And for a lot of us (myself included) we prefer to catch as many of these bugs as possible using static analysis, instead of waiting for someone to get paged when it causes an outage.
Yes, there's a trade-off, and types can be obnoxious (Java imports being probably one of the worst offenders, C++11 introduced `auto` for a reason), but that's the cost we pay.
So, please, be brave and evaluate a 100kloc codebase+deps that may contain a top level `rm -rf ~/`
Now the JS fanboys discovered and moved (from Coffeescript to Babel ESxx) to Typescript they apparently think that they can write beautiful and bugfree software just because of static type checking! Let them please move to C++ or whatever statically typed language and shoot themselves in the foot by making all those mistakes that have nothing to do with static type checking at all! Oh, and of course hitting the wall because they are missing their precious 'any' keyword!
I totally agree that type checking for dynamic languages should be done in the IDE, tooling. But static typing in the dynamic language world is a hype at the moment, so we'll have to go through a wave static type checking frenzy. For my work I look at horrible code bases, perfectly typed and strictly formatted by tslint..
But it is always an incomplete solution because a) old code needs to be retrofitted, see TypeScript's way of defining type maps for vanilla JS or b) more commonly you keep the code around that's using unsafe types, effectively passing void*|Object|"choose your poison" around.
The only difference is that Stipe has foresaw the problems and has been working on productivity for quite a while, with a dedicated group of people who help our engineers by building tools and abstractions. For example https://youtu.be/lKMOETQAdzs is done by the same org couple of years ago.
Second, compilers can use type information (or inlining hints, etc.) to compile efficient machine code, which you can inspect with the built-in function 'DISASSEMBLE.
Third, types are used in the CLOS system to support efficient multiple dispatch for multimethods, and the rest of the CLOS and MOP machinery that makes it a very "non-traditional" (despite CLOS being first to ANSI standardize) OOP system with a lot more power than other fashionable languages provide. I'm basically in agreement with the title of http://www.smashcompany.com/technology/object-oriented-progr... with the caveats that Lisp is different and an exception (even if not perfect, there's an unfortunate mismatch in methods not allowing any type for dispatching on, though you can work around it in a similar fashion to Clojure's multimethods of dispatching on a runtime value) and that carefully designed Java can make the forced OOP tolerable.
CL is also super dynamic and lets you redefine basically everything so none of this is truly "static", and that's why compilers will warn rather than error, and runtimes while developing will preserve your state and drop you in a debugger instead of destroying state, printing a stacktrace, and giving up, because you can fix it and recompile that little bit or rerun after defining a missing variable. CL's conditions and restarts system has yet to be convincingly cloned by other languages.
Contracts, DSLs, immutable data structures, simple primitives, and other things (some not present in any other languages) are available in CL... My own reading has found that a lot of them have been there or in the predecessors to the CL standardization or in things built on top since for a long time (some well before I was born) and explored by big production business applications, not just academic exercises... Some of course are more modern transplants as they haven't gotten popular until recently. But for the things that were there already, in a sense the discussions have already been done and may help explain the lack of driving force for them now. In other languages I see them driving themselves close to where CL already is more often than driving to a completely new place (but then CL provides and lets you go there too, or at least somewhere close). You're free to use these things in CL, or not; you're right that it's not an opinionated language and I'd argue never was. Fortunately there's enough capability for modularization that we can have different opinions (e.g. the meaning of syntax like [Click me] in a UI component) and still trivially share code. It's a shame that Clojure code and CL code can't be trivially shared.
> I'm not sure we'd make constants with unicode like > BigMap‹Address→Account› for generics, though, how do > you even type that? :)
I see you managed to type it! What's your secret? :-). By the way, you can use the alternate ASCII-style e.g. BigMap.of(Address=>Account).
Are you not going to use StackOverflow because it is written in C# using Microsoft servers instead of Go on Linux?
Did you not use Twitter originally because it was built on Rails?
10 years ago... what CMS was popular in a static typed language? Hell today... what statically typed CMS is popular?
But if that still matters to someone, PHP now supports types!
An always-on static type system could not develop this way.
I don't want to go back to having to keep C header file in sync. Your IDE can hide that information from your as well, if you don't want to see it all the time.
It requires every (1) editor and IDE on the planet to add code for doing that, which means every (1) programming language on the planet needs a library for parsing such companion files, for the benefit of ??????
Except for historical corner cases such as original java with its repeated type annotations that make code with types tedious to read, I wouldn’t know what that benefit would be.
(1) that ‘every’ is a bit of hyperbole, but essentially true.
For example, let's say you have Question model with two types: MultipleChoice and ShortAnswer. In TypeScript you can model it like this:
type MultipleChoice = {
mode: 'mc'
body: string
choices: string[]
expectedAnswer: number
}
type ShortAnswer = {
mode: 'sa'
body: string
exampleAnswer: string
}
type Question = MultipleChoice | ShortAnswer
TypeScript's compiler will then enforce data structure consistency across your entire codebase. For example, if you were rendering a question in React: type Props = {
question: Question
}
const MyComponent = (props: Props) => {
props.question.body // Ok, since all questions have a body
props.question.choices // Type error, since only MC has choices
if (props.question.mode === 'mc') {
props.question.choices // Ok now, since we checked the mode of question
}
}
You can also use these types to force certain code to always be correct. For example, if you wanted to display a human-readable version of a Question's mode, you could write: const prettyQuestionType: Record<Question["mode"], string> = {
mc: 'Multiple Choice',
sa: 'Short Answer',
}
and now TypeScript will force prettyQuestionType to contain keys for all modes. That includes when you add a new Question mode later.Once you learn how to lean on the type checker, you think less about such details, and your mind becomes freer to think at a higher level, increasing your overall productivity. There is a learning curve though, so be aware.
[0] https://fsharpforfunandprofit.com/posts/designing-with-types...
https://sorbet.run/talks/RubyKaigi2019/#/14
But we'd like to catch more than just errors related to constants, like those related to missing methods, or calling a method with the wrong arguments. Errors like these are only reported in files marked `typed: true`:
https://sorbet.run/talks/RubyKaigi2019/#/19
Sorbet doesn't need to have method signatures to know whether a method exists at all, or what arity that method has.
But more than that, Sorbet ships with type definitions for the standard library. So you don't even need to start annotating your methods to type check the body of your methods, because most of the body of your methods are calling standard library things (arrays, hashes, map, each, etc.).
The statistics in those slides are sharing "out of the box, what's the highest strictness level that could be added to a file without introducing errors?" So ideally an entire project be made `typed: true`, but Sorbet can be adopted gradually, so a project can exist in a partial state of typedness. We wanted to see how painful it would be to adopt Sorbet in a handful of large, open source rails projects, and it turned out to be not that bad.
[1]: https://sorbet.org/docs/static#file-level-granularity-strict...
In other statically typed langues like Rust the type system itself eliminates the need for a lot of these tests but at the cost of mental overhead of expressing your logic in a way which will satisy the type system.
Javascript and ruby, the underlying types can change depending on where the code is in execution - a variable holding a 1 can turn into a "1" and back (implicit type conversion - try 3 * "3"). This leads to a whole class of bugs not possible in a statically typed codebase where explicit conversion needs to happen - I have no hard data, but I remember debugging this type of stuff far too often and far too many times when I could've spend my time better elsewhere. (but I actually like ruby a lot!)
Type checking is not the same as being statically vs. dynamically typed!
This is true of Javascript, but not of Ruby.
irb(main):001:)> 3 * "3"
TypeError: String can't be coerced into Fixnum
People commonly conflate dynamic typing with weak typing, Ruby has the former, but not the latter (with some explicit exceptions, e.g. to_ary and friends).That's not to say you can't still end up with some interesting problems though -- if we just slightly change your example:
a = 3
b = 3
# later...
a = "oops"
product = a * b
# product is now "oopsoopsoops"
But this isn't due to automatic "weak types" style coercion -- just that Ruby lets you build a repeated string by multiplying a string by a number.Generally, there is this huge disconnect between how code is expressed as text and how it is handled in as a graph structure inside the tooling. It is soon time to move beyond simple text files for code, I believe.
Depending on what you want to do, you might also want to start with the types and have the computer figure out the implementation.
> It might even not show you types as code at all and by default and overlay/add this info only on request.
Types annotations are often are great documentation, and there's a lot of practical knowledge in the ML communities about what types annotations to show in the source for helping with understand and debugging and which ones to leave out as clutter.
I wrote 'proper type inference' above, because it's much more powerful than the watered down version Go and C++ give you. See eg https://news.ycombinator.com/item?id=8447280 for a Rust example.
How does nobody(?) support this yet?
Python supports some parts: you can subclass int, and you get all of the int methods like addition and subtraction for free, but "distanceInKm + distanceInKm" gives you an int instead of a distanceInKm; and "distanceInKm + distanceInMiles" gives you an int instead of an error.
Rust also has partial support but from the other end: distanceInMiles and distanceInKm can be two distinct subclasses of int, and adding them together is a compile time error. But also adding distanceInMiles with distanceInMiles is a compiler error, because these are basically "completely new classes" rather than "subclasses of int", and so you have to implement add / subtract / stringify / etc for yourself for every type D: (I'm fairly new to Rust so if there is a shortcut there that I'm missing please do point it out)
newtype Pixel = Pixel Int
newtype Em = Em Int
pixelWidthToEm :: Pixel -> Em
pixelWidthToEm (Pixel px) = Em px
You can try to call `pixelWidthToEm` with anything other than pixels and it won't work.More dynamically, with an open type variable that only exists in the type system:
data User a =
User { name :: String, socialSecurityNumber :: String }
data LogSafe
data LogUnsafe
logUser :: User LogSafe -> IO ()
logUser = undefined
makeUserLogSafe :: User LogUnsafe -> User LogSafe
makeUserLogSafe = undefined
We can never log the user unless the user is deemed LogSafe and we make functions that produce log safe users that you have to call before hand, in order to make sure that sensitive data isn't printed to logs.These are things that have been around for a long time in almost every type system, but people's general lack of interest in using type systems to help them conspires to keep them in the dark.
Here's how you can create a number type distinct from other number types in TypeScript:
type DistanceInPixels = number & { readonly __newtype__: "DistanceInPixels" }
And a type alias that allows you to create them: export type Newtype<T, Tag extends string> = T & { readonly __newtype__: Tag }
type Pixels = Newtype<number, "Pixels">In any case, to answer the question of "How does nobody(?) support this yet?", have you heard of https://frinklang.org/ ? It's not a useful tool for most codebases I work on but it's an interesting idea.
Eeeeeeeh…
In reality this is fast enough for most tasks.
Might have to learn me some Ruby just to figure out this mystery.
We had them as hashes for a while, but it meant that code in all sigs was loaded as the code was loaded, even if runtime typechecking was disabled. We were forced to load all constants in any signature in a file, an effect which cascades quickly. It had a big impact on the dev-edit-test loop.
For example, if we're testing `method1` on `Foo`, but `method2` has a sig that references `Bar`, we'd have to load `Bar` to run a test against `method1`.
Now sigs are blocks and lazy, and we pay that load penalty the first time the method is called and a typecheck is performed.
sig {{name: String, returns: Integer}}> For my work I look at horrible code bases, perfectly typed and strictly formatted by tslint.
There is no language that can stop people from producing horrible code.
That's bold. Do you think no developer would be able to manage it without TS? In that case you must be a fanboy!
And honestly, are you not using 'any'? And do you think your app cannot crash because of a type error at runtime? And do you trust all the third party libraries you are using that they always provide you with consistent types, also during runtime? I ask this because most TS proponents live in some kind of dream.
1. I don't have the mental capacity to keep every single function's argument/return shape in my head, or to manually check it every time I make a change. Unit tests can't deliver 100% code coverage in practice.
2. Nor do I want to perform refactorings with stone age tools like s/setFoo/setBar/g. Setting up type information lets my IDE understand which calls to .push() deal with a native Array and which ones deal with my own class, so it can rename the latter ones when I ask. I can also use tools like "Find References" and avoid false positives.
3. I'm not a one-man-band. My coworkers need to deal with this project too, and new developers need to be introduced to it from time to time, and types serve as documentation and guard rails for them much better than jsdoc or regular comments. (This also serves as a significant barrier against using anything more esoteric like Elm, because nobody around would be familiar with it. TypeScript adds just enough syntax on top of regular JS to keep JS users comfortable.)
---
I do use `any` (and `unknown`), I have no doubts that an edge case can crash my app because I didn't validate something, and I never expect third-party code to work flawlessly whether it has types or not. Rejecting TS completely because "but run time loopholes" is throwing the baby out with the bathwater (or to put it in a more hyperbolic way, being an anti-vaxxer because vaccines are not 100% safe from side-effects). TS and types are an additional safety net/force multiplier†, not a silver bullet. (That said, what is a silver bullet? Because I'd sure like one.)
---
† Only applies to a project that has passed its initial rapid prototyping phase. During wild prototyping rides, types can indeed slow you down. But that's really the same debate as RDBMS vs NoSQL.
Dart had gradual types but didn't enforce them at runtime because of performance. The PyPy devs don't believe that type annotations help them for performance (http://doc.pypy.org/en/latest/faq.html#would-type-annotation...). Also there is no JS engine that uses TypeScript annotations so far to improve performance.
Types are usually on the wrong boundary: e.g. Integer doesn't state whether that value fits into a register or is a Bignum.
Also: Aren't some type checks quite expensive? So more expensive than a simple map/class/shape check? E.g. passing an array from untyped code to a signature with something like `Array<Integer>`. Wouldn't a runtime that verifies signatures have to check all elements in the array to be actual Integers?
To me, it feels that there is a very thick wall in between high level languages and something with raw data access like C, C++, and D. You either completely throw out every convenience feature, or go all in on them.
In C, a lot of data access turns into single digit number of load/store or register access instructions. It is easy to see that it is close to impossible to add fancy data access functionality on top of that without going from single cycles to kilocycles.
I was once told "when your try improving a programming language performance, it eventually turns into C"
P.S. on JIT - it is not given that a JIT language be automatically faster than a well written interpreter on a modern CPU. One of early tricks of making fast interpreters was to keep as much of interpreter in cache and data in registers as possible to benefit from more or less linear execution flow of unoptimised code in comparison to unpredictable flow of JIT made executable code. Today, with 16MB caches, I think the benefit of that will be even bigger.
Which is kind of ironic given how bad C compilers generated code during the mid-80s, versus other mainframe languages.
That is why we need something that offer 80% the Speed of C, 80% of Simplicity / expressiveness of Javascript / Ruby, and 80% of ease of long term maintenance of a functional PL like Ocaml.
I actually think Java will one day evolve very close to that goal.
I doubt a competent JIT is ever slower than a competent interpreter, but it may not be that much faster or worth the workload.
It depends on the size of the primitives. An array language could be close to 1:1, while for a cpu-level instructions you will struggle to reach 1/6 of JITted perf.
Async python is an absolute joy to develop with.
I was playing around with adding types to everything in my program. Creating kind of a little Haskell style script. I was sad when it ran slower than it did without the types. Someone informed me that that is the expected outcome because (as you said) type checking is done at run time.
So I'm very curious as to what code exposed a slowdown after explicit types were added.
Edit: explained better here: https://github.com/Homebrew/brew/issues/3056#issuecomment-32...
Brew's not even consuming much system cpu time during `brew search`, despite hogging a core for a full minute.
Even just `brew help` takes almost 10 sec cold.
> time brew help
> brew help 0.55s user 0.26s system 96% cpu 0.849 total
aiohttp: web framework
aiopg: async postgres driver with SQLAlchemy support
asyncssh: async ssh library with SFTP capability
I generally work on CRUD microservices to automate some steps of a business workflow - activating/registering a resource with our vendors, generating and updating pricing, picking up new files off an FTP site and processing.