Live data from Hacker News

The Anti-Human Consequences of Static Typing

jeapostrophe.github.io

61–67 of 67 posts

Re: The Anti-Human Consequences of Static Typing

#61
post #59
post #52

Earlier quoted context omitted.

You just define a type that can be either a string, or the symbol "None" And as long as I know in advance that that's the type mix I want, and as long as it never changes, that works. But one doesn't always know in advance, and the type spec you need often changes. Also, the example I gave was easy because the mix was between built-in types. What about if I want to mix custom-defined type A from package P1, custom-de…

> With static typing, I need to declare a new mixed type for each mix, and keep each declaration up to date. That looks like a programming and maintenance nightmare to me for a non-trivial program. Sounds like you're thinking of Java/C# level type systems here. Type inference is a powerful feature that allows you to just not specify types (except where the program is ambiguous) in most cases.

Type inference is a powerful feature that allows you to just not specify types (except where the program is ambiguous) in most cases.

But type inference only works if there is some non-trivial base type shared by all the types I want to mix. Type inference can't figure out that None and a string are both valid return values for some function; I still have to declare a custom type that mixes them (like a Maybe in Haskell). Similarly for types declared in completely different packages that don't share any base types; they might be perfectly duck-type compatible, but how does the type inference system know that?

Re: The Anti-Human Consequences of Static Typing

#62
post #13

Are there any languages that have a type mismatch warning rather than a type mismatch error? Something like how gcc warns about incompatible pointer types when compiling C programs, but lets you use them if you insist, but for all types?

I suggest reading about progressive types: http://blog.brownplt.org/2012/09/01/progressive-types.html

Wow, thank you, that is exactly what I had in mind

Re: The Anti-Human Consequences of Static Typing

#63
post #60
post #58

Earlier quoted context omitted.

Your intentions changed, so of course you need to communicate that. In real programs (like the Haskell compiler), the programmers simply make the change they'd like to make at one point. The compiler then spits out a list of issues the change caused and provides a possible fix for each one. They continue the dialogue with the compiler by making the suggested fix, or by making one of their own devising. The end result…

Your intentions changed, so of course you need to communicate that. But in a dynamically typed language, I don't have to communicate anything; it all happens automatically. The end result is a program that's free of entire classes of stupid bugs. A good test suite achieves the same objective with a dynamically typed language. There are tradeoffs both ways; I'm not saying dynamic typing is always better. But I don't t…

> But in a dynamically typed language, I don't have to communicate anything; it all happens automatically.

Nothing happens automatically, the compiler/interpreter just doesn't check. Eventually you hit the bit of nonsense code your change caused and then the program dies.

> A good test suite achieves the same objective with a dynamically typed language.

With considerably more effort and with no guarantees that you've come even close to correct. A test suite on top of a powerful static type system at least guarantees that you've killed classes of dumb bugs, and you've thought through and tested more complicated issues.

> With dynamic typing, no inference is necessary at all.

So...? You also don't get any guarantees.

Re: The Anti-Human Consequences of Static Typing

#64
post #61
post #59

Earlier quoted context omitted.

> With static typing, I need to declare a new mixed type for each mix, and keep each declaration up to date. That looks like a programming and maintenance nightmare to me for a non-trivial program. Sounds like you're thinking of Java/C# level type systems here. Type inference is a powerful feature that allows you to just not specify types (except where the program is ambiguous) in most cases.

Type inference is a powerful feature that allows you to just not specify types (except where the program is ambiguous) in most cases. But type inference only works if there is some non-trivial base type shared by all the types I want to mix. Type inference can't figure out that None and a string are both valid return values for some function; I still have to declare a custom type that mixes them (like a Maybe in Hask…

If String and None are both valid return types for a function, then you tag that so the inference system knows it's a valid solution -- the inferred type becomes Maybe String. The compiler will even help you get there.

> Similarly for types declared in completely different packages that don't share any base types;

If you know they share functionality, you can declare that they share functionality (any language with type classes). Once you've done that, the type inferencer will generalize the type of the function to the entire class. As a bonus, you automatically factor out shared code and make the program as a whole more modular.

The answer to "how does the type inference system know this?" is usually that you've made those bits explicit. This sounds tedious right up until the first time you write a complicated program, get it to compile, and it just runs correctly the very first time. It gets better when you learn to work with the compiler and start to make judicious use of code holes (undefined/_ in haskell; they allow you to provide an expression of any type so you can test unfinished programs. The compiler can even tell you what type the unwritten code needs to have, and in most cases that actually tells you what the code needs to be).

Lastly, in a language like Haskell, type declarations are almost always optional. They're used as a kind of documentation and an enforced contract for any other programmer that happens to read the code. Most types are inferred -- if there's some ambiguity, the compiler will let you know, and you can fix the code. For example, if your function tried to return String or Nothing, the compiler would complain that String doesn't match Maybe a, try Just instead. So you replace with Just , and now the function compiles. If the requirements later change, then you make the necessary changes the same way you would in a dynamic language, and then follow the chain of type errors to fix issues that have cropped up. So if suddenly you need String or Integer instead, you'd just replace Nothing with the you now need to use. The compiler will complain that Maybe String doesn't match Integer, and so you fix your code to Right and Left . Now the compiler agrees that everything makes sense again, and you continue on.

Re: The Anti-Human Consequences of Static Typing

#66
post #63
post #60

Earlier quoted context omitted.

Your intentions changed, so of course you need to communicate that. But in a dynamically typed language, I don't have to communicate anything; it all happens automatically. The end result is a program that's free of entire classes of stupid bugs. A good test suite achieves the same objective with a dynamically typed language. There are tradeoffs both ways; I'm not saying dynamic typing is always better. But I don't t…

> But in a dynamically typed language, I don't have to communicate anything; it all happens automatically. Nothing happens automatically, the compiler/interpreter just doesn't check. Eventually you hit the bit of nonsense code your change caused and then the program dies. > A good test suite achieves the same objective with a dynamically typed language. With considerably more effort and with no guarantees that you've…

Nothing happens automatically

If I have a function in Python that used to only return strings, and I re-code it so it returns None in some cases, I don't have to re-declare anything. The code just runs and returns None automatically, without me having to change anything about the function except the actual working code that determines what gets returned. With static typing, I need to re-declare the function's return type; it won't automatically return None if the type system thinks it can only return strings.

With considerably more effort

I don't understand this; you're comparing a test suite on top of dynamic typing, to a "test suite on top of a powerful static type system", so you're expending the effort on the test suite either way.

and with no guarantees that you've come even close to correct.

Static typing doesn't guarantee program correctness either; it just guarantees type correctness, so to speak. You can have a program that gets all its type declarations right but still has faulty logic. So you're still going to need a test suite either way, as I said above.

Eventually you hit the bit of nonsense code your change caused and then the program dies.

Why do you assume the code change produced nonsense code? If it doesn't--if I got the change right--then the program runs fine.

I assume what you really meant is, if I made a mistake, a dynamically typed language won't catch it until runtime, whereas a statically typed language will catch it at compile time. That's true; that's one of the tradeoffs I mentioned between static and dynamic typing. With static typing, I catch a certain class of errors earlier, but I pay for that by having to explicitly declare things. With dynamic typing, I don't catch those errors until later, but I can program faster (at least for certain kinds of applications) because I don't get bogged down in having to keep all the type declarations straight. As I said in another comment upthread, I'm not saying this tradeoff always works out in favor of dynamic typing; but it doesn't always work out in favor of static typing either. I think it depends on the type of application you're writing.

Re: The Anti-Human Consequences of Static Typing

#67
post #64
post #61

Earlier quoted context omitted.

Type inference is a powerful feature that allows you to just not specify types (except where the program is ambiguous) in most cases. But type inference only works if there is some non-trivial base type shared by all the types I want to mix. Type inference can't figure out that None and a string are both valid return values for some function; I still have to declare a custom type that mixes them (like a Maybe in Hask…

If String and None are both valid return types for a function, then you tag that so the inference system knows it's a valid solution -- the inferred type becomes Maybe String. The compiler will even help you get there. > Similarly for types declared in completely different packages that don't share any base types; If you know they share functionality, you can declare that they share functionality (any language with t…

If String and None are both valid return types for a function, then you tag that so the inference system knows it's a valid solution

In other words, I need to do extra work to tell the type system about the function's return type.

If you know they share functionality, you can declare that they share functionality (any language with type classes)

Same comment here; the language is making me do extra work that I don't have to do in a dynamically typed language. So there's a tradeoff.

This sounds tedious right up until the first time you write a complicated program, get it to compile, and it just runs correctly the very first time.

See, here's the thing: I often write non-trivial programs in Python that run correctly the very first time. So I've had this same experience in a dynamically typed language.

Perhaps a language like Haskell would raise the bar, so to speak, for how complex a program can get and still allow this to happen; but even then, it would only matter if you were writing the particular kind of program that can benefit from the difference.

Lastly, in a language like Haskell, type declarations are almost always optional.

It's the "almost always" that's key here; basically, what the rest of your discussion here shows is that the type declarations end up not being optional in precisely the cases where, in a dynamically typed language, you would just be making changes and running code, not spending extra work on adding type declarations to resolve ambiguities for the compiler. So there's a tradeoff.

As I've said in several comments in this thread, I'm not saying dynamic typing is always better; I'm saying it depends on the kind of application you're writing. There are tradeoffs involved, and they don't always end up favoring static typing.

Post reply on HN