Hacker News new | ask | show | jobs
by wz1000 3156 days ago
A dynamically typed language is a statically typed language with precisely one type.

It is extremely easy to use haskell in "dynamic mode". Just use `ByteString`(or Data.Dynamic for safety/convenience) for all your data. Types just present a way to encode some statically known guarantees about the structure of your data/code. You are free to not encode any properties if you want to.

But it is very rare that the data you are working with requires the full generality of `ByteString`. You usually have some sort of structure rather than just working with strings of zeros and ones.

2 comments

> A dynamically typed language is a statically typed language with precisely one type.

While technically true, saying this is about as useful as saying "You can do anything in any Turing-complete programming language."

No, it's really not equivalent to cracks about Turing completeness.

Doing "dynamic" typing in a static language requires me to add all of 5 characters, e.g. ": Any"

Doing static typing in a dynamic language requires me to write a type checker.

These are nowhere near the same.

Being able to work with "Any" implies either working with Strings (since you can encode anything in strings) or it implies a memory unsafe language (e.g. working with void* in C) or it implies subtyping and hence an OOP language.

But OOP subtyping is already about solving polymorphic call sites at runtime. And because you carry a vtable around for every instance, thus objects being tagged with their classes, you can always do upcastings and downcastings. So OOP languages are already very dynamic on that scale and fairly unsafe.

No, you cannot do ": Any" in Haskell.

On second thoughts I think kod used Any to mean something like Dynamic and bad_user assumed Any meant a top type, something like I believe Scala has.
Clojure has type hints, I can add ^int to a symbol and get some “static typing”. That is: shitty static typing. However, this is about the same as the average Any type in a static language. In order to implement a good Dynamic type, you’d need to implement reflection, caching dynamic dispatch, etc. Haskell’s Typeable is an OK implementation, but not nearly as good as say the JVM’s or JavaScript, despite their many flaws.
The point is, types give you an option to encode invariants at compile time. You can choose to use this to your advantage, or not use it at all(use ByteString for everything).

With dynamic types(or just one type), you don't even have the option to do this.

Except I really don't have that choice because the language and library design matters. If I chose to use ByteString for everything, I'd first have to implement Tcl in order to get anything done.

But, yes, you're right, most dynamic languages lack good tools for stating invariants and checking them early. I would like to see that change. However, I'd rather the solution account for runtime dynamism, extensibility, and partiality. We're _slowly_ getting there with more and more advanced type system features. It's time to take that knowledge and repackage it at the foundational level of typed languages.

There's just one type at compile time, but many more at runtime. This is still a strongly typed language. The only problem is our static analysers are too dumb to prove things without providing ample explicit hints, or changing the way we code to restrict certain ambiguities that it can not resolve at compile time. Haskell has chosen to try and push the boundaries of such static analyzer, but there's still limits, and it can't infer everything, and still restricts certain designs. I admire it for its efforts.

Clojure has a different strategy, it creates a new time, REPL time. So you can test your types at REPL time. Not when it compiles, but a little before it runs. It won't prove what you don't try though. So in practice, its using a statistical model where the programmer is the heuristic. You best guess the edge cases, and try them at REPL time. This will not catch all static errors, but will also catch some runtime errors. So it creates a disjoint set of errors that it catches. This is a trade off. Static types and REPL time will catch some of the same things, but also different errors.

Now both adding static type info, and doing REPL time testing comes to a cost to the programmer. Its one more thing we have to do. Some like me, fond more value most often at the REPL, it helps me explore and innovate my code, and is just more fun to me. I also prefer the kind of bugs it catches. Others think the opposite.

What most people seem to agree on though, is that doing both is way too much effort. That's why you don't have REPL time be a popular activity in Haskell, or core.typed be popular in Clojure.

> That's why you don't have REPL time be a popular activity in Haskell

Is it not a popular activity to use the Haskell REPL (ghci)? I though it was pretty common to use it when developing code, though I admit I don't have any hard data.

The Haskell REPL is pretty good for a static language, but the experience is dramatically different to how a Clojure programmer would use want to use it. To be fair, Node and Python also have totally not usable REPLs for this style.
GHC's repl is completely fine.

The thing with dynamic languages is that the development style is basically println-driven.

It goes like this: because you can't keep anything longer than a 1-page script in your head and because you can't remember the APIs of other people and hence you can't trust anything you write, in order to keep some sanity, you have to execute every freaking line of code that you write in order to verify that what you wrote actually works — and the sooner you execute, the better, because if your program crashes, the triggered error can happen far away from where the mistake is actually made.

This happens for every dynamic language, not just Clojure. This is why the read–eval–print loop is so important.

However the development experience changes dramatically in a good static language (no, not talking of Java or Go), because you can write more than one line of code before feeling the need to verify it — when compiler type checks a piece of code, at the very least you can be sure that the APIs you used, or the shape of the data you're interacting with are correct.

Refactoring is also painless. Ever done refactoring of projects built on dynamic languages? It's a freaking nightmare and no, the tests don't help that much, the tests actually become part of the problem.

This is also why dynamic languages folks complaining about long compile times are missing the point — those long compile times are necessary to give you guarantees that in a dynamic language you don't get at all, changing the experience, because in turn you don't have to run your code that often.

If no other language has any functionality similar to how Clojure does things then I think we'll need references to explanations or videos before we can even begin to understand your claims!
"A dynamically typed language is a statically typed language with precisely one type."

You still have types; they're just not checked at compile time.

You can have dynamic "type-tags" for your unityped language too. That is what is done in the EDN ADT described in the article.