25 ms·
It's been funny to watch how more and more static type systems are getting bolted on to dynamically typed languages in recent years. Typescript (with stellar a
by the_duke 7y ago
It's been funny to watch how more and more static type systems are getting bolted on to dynamically typed languages in recent years.
Typescript (with stellar adoption), native type annotation support in Python, Sorbet, PHP 7, Elixir + Dialyzer, ...
I wonder why there isn't a popular gradually typed language that natively allows writing both dynamic and type-safe code, allowing quick prototyping and then gradual refactor to type safety.
I guess in part because it's a big challenge to come up with a coherent type system that allows this, the bifurcation in the ecosystem, and often a somewhat leaky abstraction. Eg in Typescript you will often still run into bugs caused by malformed JSON that doesn't fit the type declaration, badly or insufficiently typed third party libraries, ....
Google's Dart is the only recent, somewhat popular language (only due to Flutter) that allows this natively - that I can think of right now.
I do think such a language would be very beneficial for the current landscape though, and projects like this show there is a clear need.
Edit: just remembered another option: Crystal. Also Julia, as pointed out below.
- jez 7y agoHave you seen Julia? https://julialang.org/ https://julialang.org/ The first three selling points on their home page are Fast, Dynamic, Optionally Typed.
- deleted 7y ago[deleted]
- Buttons840 7y agoMeh. Julia does nothing to help me catch errors before runtime, it's no different than Python in this regard. Although it does use the types to generate fast code (and in my experience it does live up to its performance claims). I've seen some talk of Julia doing compile time checks, maybe in the future it will?
- ddragon 7y agoJulia's type system works to make the language more dynamic instead of more static, instead of one slow program in which types have to be checked at runtime a Julia program is a superposition of every optimized implementation of an algorithm and explicit types are used to access and manipulate a subgroup of implementations. So in general you don't use types to increase performance (the compiler already infers all types to generate fast code, including optimized union types for nullable fields for example), but when you want to restrict the polymorphism to work in more specific code. But since the JIT compiler infers all types, regardless of explicit hinting, it's possible to create tools to evaluate all types before a function is called [1], which combined with the efforts in better precompiling the code (which increases the amount of code that will have the type inferred before running) could make some good tooling for compile time checking. [1] https://nextjournal.com/jbieler/adding-static-type-checking-to-julia-in-100-lines-of-code https://nextjournal.com/jbieler/adding-static-type-checking-...
- dnautics 7y ago> a Julia program is a superposition of every optimized implementation of an algorithm and explicit types are used to access and manipulate a subgroup of implementations. That is an amazing description of what it is!
- seanmcdirmid 7y agoIsn't Julia optionally dynamically typed, not optionally statically typed?
- slushy-chivalry 7y agohere's one more (not so popular) one: https://inko-lang.org https://inko-lang.org I'm also a fan of clojure.spec, though not a substitute for a proper type system, is extremely helpful in achieving the same goal.
- agumonkey 7y agowaves and roundabout
- keithlfrost 7y agoClojure allows one to start with essentially untyped code, then add type declarations for efficiency and safety.
- kyllo 7y agocore.typed never really caught on though, what seems more popular is Schema which focuses on annotating and validating the structure of lists and maps: https://github.com/plumatic/schema https://github.com/plumatic/schema
- darpa_escapee 7y ago> I wonder why there isn't a popular gradually typed language that natively allows writing both dynamic and type-safe code, allowing quick prototyping and then gradual refactor to type safety. With the addition of `var`, I think Java is that language.
- rapind 7y agoI prefer how this evolves organically if / when there's a need. Once the need is proven by adoption numbers (say this ruby w/ type checking gets popular) you'll have a good idea of what people want (and any issues they might have with this implementation).
- yellowapple 7y ago> I wonder why there isn't a popular gradually typed language that natively allows writing both dynamic and type-safe code, allowing quick prototyping and then gradual refactor to type safety. Popularity aside, Perl 6 supports exactly this.
- jjeaff 7y agoSo does php 7
- GordonS 7y agoWell, kind of. I have an old website built on PHP, using a PHP 7 runtime, and I recently had to make a few small changes and discovered PHP had added typing in recent years. My interest was piqued, and I dug into the docs a bit. And was promptly disappointed - yes, PHP allows gradual typing, but the type system it has is woeful! Aside from the pitiful selection of types, basically only function arguments can have type hints (e.g. no typing of local vars).
- muglug 7y ago> the type system it has is woeful Check out a thing I made: https://psalm.dev https://psalm.dev. It allows you to add more descriptive types in docblocks. > no typing of local vars Why do you want explicit types for local vars?
- GordonS 7y ago> Why do you want explicit types for local vars? I suppose mainly because of deficits in the static analysis tooling that exists (or at least, that I used), as they usually cannot infer types correctly. Psalm looks pretty nice though, so I'll check that out next time I'm working in this PHP codebase :) Even with exellent tooling though, there are still occasions where I like to use an explicit type.
- the_duke 7y agoAnother huge problem is the lack of typing for arrays. Considering the prevalence of array types in PHP, this considerably reduces the available safety. Basically your only option is to use only classes for keyed arrays, and array wrapper classes like `MyClassArray` with typesafe methods like `push` and `get`. Typescript solves this beautifully generics and interface types.
- robacarp 7y ago> a popular gradually typed language that natively allows writing both dynamic and type-safe code, allowing quick prototyping and then gradual refactor to type safety. You mention it in your edit, but Crystal has been exactly that for me. A rubyist for a decade I found Crystal to have the type system I was expecting all along.
- p4bl0 7y agoI'll add Racket and its variant, Typed Racket, to the list. See https://docs.racket-lang.org/ts-guide/index.html https://docs.racket-lang.org/ts-guide/index.html
- munificent 7y agoDart isn't optionally typed any more. It's now a fully statically typed language, that also has a special "dynamic" type. That puts it in the same boat as C# and Scala, among others. Optional or gradual typing does seem like an obvious brilliant idea from the outside. Start out dynamic when the program is small, layer in types when it grows to the point where you need them. Capture the union of both dynamically typed and statically typed users. Everyone wins! In practice, we found ourselves in an uncanny valley where we were too typed for the dynamic typing folks, and too unsafe for the static ones. We couldn't deliver the user experience either camp expected. We learned, the hard way, that a statically typed language is not simply a dynamically typed language plus some type annotations. Everything about how you use the language is different. --- The way you design APIs is different Python's tuple type has a subscript operator to return an element at the given index. That's a perfectly reasonable, simple, clean API in a dynamically typed language. If you want to have statically typed tuples, that API doesn't even make sense: t = (1, True, "three") x = t[datetime.datetime.today().weekday() % 3] What is the static type of x? Another example: Python's list type has a sort() method. It takes an optional "key" argument that is a callback that converts each value to a key of some time and then sorts using those projected keys. If you pass a key function, then sort() needs to be a generic function that takes a type parameter for the return type of the key function, like: sort<R>(key: (T -> R)) But if you don't pass the key function, the R type argument is meaningless. Should it be a generic method or not? An even gnarlier question is "What kinds of lists can be sorted at all?" The sort() method works by calling "<" on pairs of elements. Not all types support that operation. Of those that do, not all of them accept their own type as the right-hand operand. How do you design the list class and the sort() method such that you ensure you won't get a type error when you call sort()? To handle this kind of stuff, the "best practices" for your API design effectively become "the way you would design it in a fully statically-typed language". But those restrictions are one of the main reasons people like dynamic languages. You can mitigate some of this with very sophisticated type system features. Basically design a type system expressive enough to support all of the patterns people like in dynamically typed languages. That's the approach TypeScript takes. But one of the main complaints with static type systems is that they are too complex for humans to understand and too slow to execute. This makes that even worse. TypeScript's type system is very complex and type-checking performance is a constant challenge. In order to let you write "dynamic style" code, TypeScript effectively makes you pay for a super-static type system. --- User expectations are bimodal Once you ask people to design their APIs such that they can be statically typed and then let them start writing type annotations, we observed that they very quickly flipped a mental bit and expected the full static typing experience. They expected real static safety where certain errors were proven to be absent. They expected the performance of a statically-typed "native" language. But most optional or gradually typed languages are unsound in order to allow typed and untyped code to intermingle. That means type errors can still sneak through and bite you at runtime. It means you get none of the compile-time performance benefits of static types. Sorbet asks you to write your code with all of the discipline, restrictions, and cognitive effort of a statically-typed language. In return, it gives you the runtime performance of... Ruby. Worse, actually, because it is checking your type annotations dynamically at runtime. It basically turns your type annotations into assertions. So you get even more potential runtime failures. This was how Dart 1.0 worked. I used to joke that we gave you the best of both worlds: the brevity of Java and the speed of JavaScript. And then I cried a little. --- This sounds like I'm criticizing this approach to languages. I actually think TypeScript, Flow, Sorbet, and others are a really smart solution to a very challenging problem. If you have a very large corpus of dynamically-typed code that you want to keep extracting value out of, they give you a way to do that while getting some of the benefits of types. If I was sitting on a giant pile of JS or Ruby that I had no plans to rewrite, I would absolutely use one of these tools. But for new development, I think you're much better off choosing a modern statically typed language if you think there's a chance your program will grow to some decent size. By that, I mean C#, Go, Swift, Dart, Kotlin, etc. Type inference gives you most of the brevity of dynamic types and you'll get all the safety and performance you want in return for your effort to type your code. If you're going to do the work to make your code typable, you should get as much mileage out of it as you can. So far, no one I know has figured out how to do that with an optionally or gradually typed language. --- This is, of course, just my personal preference. And I'm biased because I've already walk the long painful educational road to understand static types. One of the real large benefits of dynamic types is there is much less to learn before you can start writing real code. For new users, hobbyists, or people where programming isn't their main gig, this is huge. I love that dynamically typed languages exist and can serve those people. But my experience is that if you're a full time professional software engineer writing real production code eight hours a day, it's worth it to get comfortable with static typing and use it. The fact that basically every large software shop that had a big investment in dynamically typed languages is trying to layer static typing on now probably tells us something. Google with Closure Compiler and Dart. Microsoft with VB.Net, TypeScript, and Pyright. Facebook with Hack and Flow. Apple with Swift.
- cmpolis 7y agoThanks for bringing up Dart (underrated IMHO). The built-in optional typing helps with productivity and readability. There is also a great official style guide which goes over when static/inferred/dynamic typing are preferred: https://dart.dev/guides/language/effective-dart/design#types https://dart.dev/guides/language/effective-dart/design#types
- xhubin 7y agoAgreed, I really hope Dart gets more adoption now with the help of Flutter. I would especially want it to get some more back end love.
- iosonofuturista 7y agoYes, I'm watching http://aqueduct.io http://aqueduct.io and https://angulardart.dev https://angulardart.dev with interest. I'm loving Dart + Flutter, and I hope to be able to write backend code in dart as well, because I want to be able to reuse code.
- thosakwe 7y agoI'd also encourage you to check out https://angel-dart.dev/ https://angel-dart.dev/.
- zem 7y agostanza [http://lbstanza.org/ http://lbstanza.org/] is optionally typed and compiles to native.
- pbiggar 7y agoI'm actually working on solving this problem at the moment with https://darklang.com https://darklang.com. Our approach is to allow the quick prototyping of python via tooling built-into the editor, within a language that has strong static types (similar to Haskell or OCaml). As an example, you never change types in Dark, you only make new types and switch over to them, so if you want to test out a type change for just one HTTP route, you can do that. Dark also doesn't have nulls or exceptions because they're hard to reason about. The usual tools to replace them (Result and Option/Maybe types) require you to handle all the cases when you write code using them. We're allowing you to write code that doesn't handle these cases (again, using editor tooling). Instead it tells you exactly what errors can happen at every point in your code. Once you have your initial prototype/algorithm figured out, you can use that information to handle all the edge cases.
- tomp 7y agoDo you have any documentation? > Dark also doesn't have nulls or exceptions because they're hard to reason about. How do you handle OutOfMemory errors?
- pbiggar 7y agoNo documentation just yet, but we do have an introductory design doc: https://medium.com/darklang/the-design-of-dark-59f5d38e52d2 https://medium.com/darklang/the-design-of-dark-59f5d38e52d2. I'm working on an essay about this particular thing (email/twitter me for an early draft). In the longer term, OutOfMemory errors will be handled by automatically rerunning the request with more memory enabled. (Or even longer term, requests will be split and parallelized and checkpointed, etc, automatically). But yeah, if you run out of memory, it will probably simple return a 500.
- lifthrasiir 7y ago> Instead it tells you exactly what errors can happen at every point in your code. Once you have your initial prototype/algorithm figured out, you can use that information to handle all the edge cases. While I can't say much about Dark (the available blog posts [1] are shallow), I do think that the automation may be one key aspect of future programming languages. For example, when I'm thinking about gradual typing I don't only want to mix the hodge-podge unitype and actual types but also convert the former to the latter, and a large portion of the process can be automated in various ways (for example, one can track the typical runtime types that unityped variables have; the programmer can solidify compile-time types using that fact). [1] https://medium.com/darklang https://medium.com/darklang
- loxs 7y agoFast prototyping without types? Meh! I need types to be able to prototype and change things really fast, knowing that it won't break. I feel a lot more confident to prototype in OCaml/F# and then "downgrade" to an 'ordinary' language that needs more people to understand what is written, than the other way around (prototype in Python and move to something 'real' later) I think that recent movement to put types in dynamic languages is just because of the need to fix existing projects, people are finally "getting it". TypeScript is awesome in this regard. Almost makes JS bearable.
- im_down_w_otp 7y agoErlang/Elixir + Dialyzer?
- loxs 7y agoIt would be great if it had a proper IDE. That's the only major hurdle to the success of this platform.
- jswny 7y agoLSP support is pretty good for Elixir. Most of the actions are supported and it's pretty fast. There's definitely not as much drive for IDE tools as I'd like to see out of the core team/Elixir community but pretty good LSP support works in almost any IDE.
- loxs 7y agoYeah, but integrating the IDE with dialyzer is a pita. And if you don’t have that, it’s not a “proper” language
- tptacek 7y agoFor whatever it's worth and without wanting to start a language war (I like Python just fine), I think Python/Ruby-style typing is a false economy for prototyping. There are a lot of things that make Go slower to write than Ruby, but mandatory typing isn't one of them. Rather, Ruby's total lack of typing makes it harder to write: you effectively end up having to write unit tests just to catch typos. I wonder whether the perception that type safety slows down Ruby (or ES6) development comes from the fact that the type systems are bolted on after the fact.
- steveklabnik 7y agoI think about this a lot, as someone who loves Ruby and loves Rust. I think it’s because we do a bad job of looking at the total costs. It feels like you can knock something out really quickly in Ruby. And in some sense, you can! But the time you spend debugging later doesn’t get factored into the way that it feels when you’re just cranking out features.
- tptacek 7y agoYes, and, just to keep this clear: that debugging (or unit-testing-for-typos) time is coming out of your prototyping budget. Your Ruby program will fail to run for stupid reasons the first time you run it, which is not the experience Java and Go and Rust programmers have. I just remember when I first picked up Go, after about 5 years writing Ruby exclusively, how amazing it was that all my Go programs "worked" the first time I ran them. Obviously, they had bugs. But they didn't blow up for stupid reasons. I'd been a C/C++ programmer for about 10 years! I guess I just forgot that compilers can catch typos if you let them.
- go_ruby 7y agoYou never had a go program fail to compile, only to fix the problem then try to compile again? Replace 'run' with 'compile' and you could still type a bunch of go code and have to make changes before you deploy or no?
- 7y ago
- brightball 7y agoI’ve found Elixir to be exactly that.
- kyllo 7y agoGroovy is one language that is dynamic but also has an optional statically typechecked compilation mode.
- vorg 7y ago> Groovy is one language Apache Groovy is two languages. The Groovy 2.x download, first released in 2012, bundles two different compilers that were both forked from the Groovy 1 compiler. Only one of them has upgraded to the JDK-7 invoke-dynamic capabilities, and the other (which hasn't) is the one actually used by Gradle and Jenkins and everyone else. Last month, the Groovy project managers at the ASF announced they were keeping the upcoming Groovy 3 as two separate languages also. The long-awaited parser upgrade from Antlr 2 to Antlr 4 is being bolted on to the invoke-dynamic compiler only -- the compiler no-one uses. They talked about Groovy 4 reverting back to a single language, but i'm guessing that's many years away because the original purpose of making Groovy be two languages in the first place was to not change the language that users actually use in any way, while simultaneously appeasing their own developers by bundling the code they wrote (invoke-dynamic bytecode generation, Antlr 4 parser upgrade, etc) in the language.
- pjmlp 7y agoAnd if it wasn't for Gradle it would be mostly irrelevant today, so much for Grails and JEE Java's companion language for bean implementation. The recurring talks on Android build speed, with everyone moving into buck and bazel show how much everyone loves putting up with it. Just last week AMA on Reddit had the recurring answer to add memory and profile build execution. When a build tool requires profiling to make it usable, it already starts on the wrong foot.
- PopeDotNinja 7y agoDialyzer is an Erlang thing, and it's been around a long, long time. That doesn't change the point your are making, and am just clarifying it a bit.
- didibus 7y agoAs I understand, Racket seems to be the gold standard in that area.
- rlander 7y agoJust to be a little pedant, Dialyzer (an Erlang success typing lib) precedes Elixir and the other static typing efforts you mentioned by many years, way before this so called “static typing renaissance”.
- elcritch 7y agoAnother thing to point out is that (dialyzer) typespecs are extremely prevelant in both Erlang and Elixir libraries and especially the core language libraries. So not only does dialyzer precede the others, it’s become a core part of Elixir and Erlang. In contrast, mypy & sorbet appear to be largely second class tools. TypeScript though appears to have made more inroads.
- dnautics 7y agoeh I would say that dialyzer is kind of a 1.5-class citizen, at least of Elixir. It operates using a very different AST with somewhat different "ideas" of what is what, and writing typespecs in macros is not... easy.
- zeckalpha 7y agoHaskell, with deferred type errors and runhaskell, is quite a dynamic language.
- nullwasamistake 7y agoType inference is where it's at. Everybody loves types when there's hardly any overhead. Typescript is the best example of this. My theory, the first languages of most tend to be untyped. Over the years you get tired of dealing with type errors and move to more complex languages with strong typing. After a while you get tired of typing a bunch of useless crap because the compiler isn't smart enough to figure out the type for you and land in Typescript or similar
- resynthesize 7y ago> Eg in Typescript you will often still run into bugs caused by malformed JSON that doesn't fit the type declaration, badly or insufficiently typed third party libraries there's a fantastic typescript library, io-ts (https://github.com/gcanti/io-ts https://github.com/gcanti/io-ts), that provides the ability to declare runtime types variables that you can infer compile time types from that solves exactly this problem. it's deifnitely work taking a close look at if you want to ensure type safety at runtime for data coming from third parties.
- _hardwaregeek 7y agoUnless you've used a language with a high level of strictness, i.e. OCaml/Haskell/Rust, it can be hard to see the sheer power and utility of typechecking. If someone has only used Java, they may not understand the true power of types. But if you've familiar with OCaml/Haskell/Rust, why bother writing dynamically typed code? Sure there's some niche usecases where it's more powerful, but generally you can do as much with say, Rust, or even more pragmatically, C# 8/Kotlin. While if you've only used dynamic languages or badly typed languages, then having to deal with this stupid naggy compiler is just annoying. A big part of learning a strongly statically typed language is learning that the compiler is your friend, and that errors are good. I've noticed that a lot of people new to TypeScript try to get the compiler to shut up, often resorting to any or @ts-ignore, while more advanced users will see it as a dialogue. The compiler complains? Okay, something's wrong: Let's find the root cause here. TypeScript took off because people had no choice but to write JS, so any benefit was better than no benefit. Sorbet was also borne out of an existing codebase. But a new language wouldn't have this lock in factor.
- IshKebab 7y agoI don't think it is hard to see the power of stronger typing - surely everyone who has written any code in Python or Javascript has made a typo that would have been caught in a more strongly typed language?
- _hardwaregeek 7y agoYeah, you'd think. But a lot of people who write code in solely Python and JavaScript just see it as an inevitability of programming. Much as I'd imagine people thought you'd always have to manage memory when programming. Or that programming would always involve punching cards and having to debug via hex dumps. Often times when a new technology like automatic memory management or strict, sound typing becomes available, people will only see the loss of freedom/control and not the corresponding benefits. They won't see it as "oh I lose X to gain Y", but just "I lose X".
- gbersac 7y agoAdd scala to the list. Or any typed functionnal language.
- pjmlp 7y agoYou forgot two of the very first ones in this regard, Lisp and Basic. Also Dart 2.0 is strongly typed with type inference, they rebooted the type system.
- lillesvin 7y ago> I wonder why there isn't a popular gradually typed language that natively allows writing both dynamic and type-safe code Unless I'm misunderstanding something, PHP7 can do exactly that. With regards to the general trend you mention about adding static-ish typing to dynamically typed languages, the opposite is also happening to some extent. I work in a medium sized company that does mainly .NET and I see C# devs use `var` a lot (in order to let the compiler infer the type instead of having to declare it explicitly). I'm not sure if the `dynamic` type is also seeing increased use, but just the fact that it was added to the language in v4 says at least a little. I think what is really happening is that the more popular languages will sort of naturally converge as development progresses and more and more people request features. So while Mr. PHP-dev-turned-to-C# will maybe want more dynamic-ish typing in C#, Mr. C#-dev-turned-to-PHP will request more static-like typing in PHP.