Snake Oil.
Apply this claim to any set of real world functions, however well designed they are.
This is classic Haskellism. Obsessed with the lattice of types in the room. Door closed. Real world outside.
Snake Oil.
Apply this claim to any set of real world functions, however well designed they are.
This is classic Haskellism. Obsessed with the lattice of types in the room. Door closed. Real world outside.
Otherwise you hope that someone has written in a doc somewhere that bar is a string. And judging by npm docs I've seen - that is very unlikely. So you debug at runtime, head over to the github repo, or do an console.log(typeof(... hmm!
(a -> b -> c) -> b -> a -> c
1. Call the function 2. error 3. infinite recursion
Technically correct - there are infinite possible error messages.
But we could potentially have some list of exceptions.
So one valid implementation would be: If c is string we always return hello world elseif a, b and c are integers we add the two ints. Elseif a and b are the same type we call the function with the argument order swapped. And finally, if none of these are the case, we call call the function with the arguments.
Dynamic types are sometimes called “tags” to distinguish the case in which this information _does_ exist at runtime.
How about calling the function derivative?
Sounds like you could have used real documentation.
How would you write this with no constraints on the types involved?
In any case this is precisely the trouble I believe the typer enthusiast get into. You can keep pulling this yarn, refining your types to match the world as it exists today; meanwhile the dynamic typers forgot about this function days ago and are eating your lunch in productivity.
It's a lesson that could be better understood by parts of the Haskell community, to be sure, but it's not always clear how useful a tool can be when you don't really know how to use it.
And here, you have repeatedly shown that you really don't know what you're talking about - take the opportunity to learn.
To be concrete about it, that's exactly what I'm suggesting.
Please demonstrate, in any programming language, a derivative function that is parametrically polymorphic - that is, the same machine code will handle absolutely any type that's thrown at it - floating point, integral, string, function, map, set, graph, etc. I do not believe it's possible, unless you have some very different notion of derivative in mind. I would be delighted to learn it is possible, and mildly pleased to learn of a reasonable use of the phrase "derivative function" that's other than what I'm thinking and which makes sense of what you've written above.
The more concrete the type signatures become the less they become documenting.
A signature of a -> a defines the id function because the type is so generic. A function of String->String can do pretty much anything.
A type signature of Seed -> String -> Hash is pretty enlightening!
For one.
Now tell me: are types the best documentation?
f :: (a -> b -> c) -> b -> a -> c
result in f (+) 1 2
evaluating to "cookie"
? For one, the types (obviously, to me at least) don't work out.You're pattern matching on a number and returning a string. The function might be handed a function and expected to return a file handle for all you know from inside the function.
There are some really great things about types as documentation, though:
1) It's machine-checked, so it's probably not out of date or mistaken, which can be worse than no documentation.
2) The interaction with my code is checked, so if I misunderstood the compiler often has my back.
3) With inference, I can often ask for docs for code I just wrote.
None of this is to say that there aren't usually other forms of documentation that should be produced.
My experience with TS has been great until I need to deal with data that comes from an external system (API, client, database). Then I either have to cast the type (which can lead to bugs where compile and run time types don't match and IMO isn't an option server-side) or I have to effectively duplicate my types by writing type guard functions.
That said, even if I have to write type guards at the edges (which I'd do in JS anyway, just because you aren't using types doesn't mean you don't need to enforce schemas), I think being able to rely on compiler typechecking inside the codebase boundary is really nice for making module interactions more predictable.
>Change a front-end React prop, then follow static type errors through the statically typed API, into the backend, all the way down into the database where they force a change in the Postgres schema. The system won't even compile unless React can talk all the way down to Postgres.
https://twitter.com/garybernhardt/status/1140695491685933060
Here's how you know what I'm saying is true. Because you can go across the landscape of systems in the real world and see both crappy systems falling over and rock-solid systems humming along happily -- and the differentiation between these two categories is NOT a type system. It's one thing: it's who wrote it and what was their level of experience and what was their value system.
I actually agree with your second paragraph, and I like to think the systems I build fall in the latter category, but I'm still not too proud to take whatever help the compiler can give me.
- It made you top heavy and more likely to crash
- It reduced your speed in half
The tacit implication (or elephant in the room) when someone argues for types and the presumed safety they bring is the severe cost they impose. Now they save some costs too, for sure. But the balance that I see is far, far in the direction that they save much less than they cost. (When applied universally as they almost always are when applied.)The success here lied in using various tools, applied pragmatically at each step according to the given context. Probably the most important tool is defensive coding and building up a system from decoupled, almost visibly-correct components. Then some nice technical tools thrown in: dynamic development with hot loading, some modest storybooking of components, a repl, good backend libraries, conscious and measured unit testing, strong build/deploy/release tools.
The ability to turn on a dime and incrementally refactor things was paramount to the success and efficiency of this development.
Then someone says we need to use strong static typing. All the way. No judicious application of this tool, we'll use it everywhere. We'll make everything cohere to the type system. This is no pragmatic choice. This is religion. You know its religion because its an ideology that pervades the whole system. There is no judicious application. The extreme tax of contending with types as your system evolves is high. I've only ever seen strong typing enthusiasts deny this truth. Will one enthusiast be honest here? You can literally stand over a strong typing enthusiasts shoulder as they spend an hour running all over their system adjusting their types for one minor change in business requirements and they'll still say, "No, no, types don't cost anything..."
Static analysis doesn't solve all your problems, but it solves enough of them to be a very useful technique.
And often -- very often -- especially in strongly typed languages -- the static checker will reject code that is otherwise valid. First point.
Second point: Statically typed PLs (especially strongly typed ones) enforce the verifications across the code, with no (at least non-ridiculous) way for the coder to be judicious about things.
Third point: statically typed languages have weaker runtime features/abilities: polymorphism, homoiconicity, true REPL (ie true read->eval), etc.
All of these points add up to MORE cognitive complexity for the coder who is trying to develop non-trivial business applications -- not less cognitive complexity.
It's not being smart. It's learning. Once you learn algebra, say, many classes of problems without algebra would be too much cognitive complexity.
Type systems are focused on one thing: type coherence. But the real world demands runtime dynamics are what the customer is paying for; being as close to that need as possible IS lower cognitive impedance.
I've worked with a ton of Python code that looks like this:
def foo(bar):
# do stuff
return baz(bar)
What does `foo()` return? I have to go read the body (or docstring if I'm lucky) of `baz()` to know that. And `baz()` might be another level of indirection to `quux()`. If I change what `baz()` returns, now I need to grep my codebase for all call sites of `baz()` and verify that the new return type is acceptable at each of them, and make changes if not. This is super time-consuming and error-prone, especially when a compiler (especially with an IDE refactoring tool) could do take care of it for me in seconds. It's easier if I have a good test suite, but that means I'm just implementing static type checking with runtime tests, which is more code that I have to maintain.Why does the blame always go to lack of types?
Whether or not you have types you're still going to suffer if you don't have the other things I mention. And if you have the other things I mention then static typing become much more of a nuisance. Ergo...