> I'm generally a proponent of "strongly typed" languages. But I do worry about the cognition overhead the syntax causes, and I do wonder if there is a "better way".
What about dynamic language + runtime type checking + (nearly) 100% test coverage?
You get the flexibility of a dynamic language and the safety of a static type language (in fact you get more, since runtime type checking is more powerful).
100% Test Coverage means that every code path is executed by the test suite. This is probably a good thing.
Yet it does not mean that the code is correct for any definition of "correct" other than "passes the associated test suite." One unit test makes a line of code covered. It does not make the domain of inputs covered - 100% coverage does not mean that corner cases have been handled correctly - or that they have been considered at all. Again, all it means is that every line of code was executed at least once.
There are useless tests:
def test167 [
if [ val == foo(8); return true]
then return passed
else return failed
endif ]
enddef
It can be very costly to achieve 100% test coverage and static verification can reduce this cost by giving you some tests "for free". For example, you never need to write a test to check for null-pointer-exceptions if you code in Haskell.
Static types also help in situations other than testing. For example, you can use "guardrail programming" to be much more liberal when refactoring things: want to change a function signature? Just change it and the compiler will point out what other parts of the code need to be updated. This is even if you haven't written a test suite yet or are doing big changes that invalidate the old test suite.
That said, I think the perfect world would be one that lets you mix dynamic and static typing in a single program (some things are hard to verify statically, but would be nice if we could at least express them as types) but I think we are still a way off that being a practical alternative.
Yes, C# and lots of other language have that feature. Where it gets really tricky is if you want to dynamically check the type of functions or objects, since you need to run the assertions when the function/method gets called rather than when the function/method gets assigned to some reference. Its also tricky to give a parametric type to code that is implemented with dynamic types.
Probably you are right, but for a library I think it should be an obligation to provide 100% coverage. For a generic program, maybe I sould provide 100% coverage at least of the "critical paths".
I still don't understand the notion that dynamic types gives you anything more than static types. Are there really usefull programs that can't be written in, say, Haskell?
IMO, its not just about what useful programs you can write. Its also about how much of the type system you need to learn to get the program to compile. For example:
function foo(f){
return [f(["1", "2", "3"]), f([1,2,3])];
}
function length(xs){
return xs.length;
}
foo(length);
Anyone can see what this Javascript program doing but to write the Haskell equivalent you would need to understand higher-rank-types and enable the corresponding language extension, which gets in the way of beginners.
Unfortunately I do not know so well Haskell to give you an answer. I was referring to this:
> I think this is because there is yet to be a language that makes expressing complex types easy.
Since I'm obliged to use a dynamic language (JavaScript) I'm looking for a solution in it.
And in JavaScript is really easy to express a complex type, say "the set of all the even integers between 2 and 14 but 10", using runtime checking to enforce the constraints. Is it simple in Haskell to define such a set in its type system?
Simple? Yes. Pleasant? Not really. I'm not a Haskell expert but I can write that type in Scala (the Haskell version probably has less boilerplate, but still some):
Like I said, it's cumbersome, but there's nothing actually complicated about writing this, it's just boilerplate to represent it in the type system.
On the other hand, in Idris or the other languages mentioned in the article, you should be able to write that kind of constraint at the type level just as easily as you can at the value level. That's where it gets really exciting.
In javascript, any method that relies on receiving a value of "the set of all the even integers between 2 and 14 but 10" should probably check that it did in fact receive such a thing or it might be buggy. In a statically typed language, that check can easily be centralized to wherever an `EvensFrom2To14Without10` value is constructed.
Here's an example in rust[0]. If the `print_bare` method relies on only receiving even numbers between 2 and 14 excluding 10, then it is buggy, because it isn't checking its input, but the `print_tagged` method is correct without needing to check!
That's not really expressing a complex type in JavaScript, its expressing a complex (such as it is) runtime constraint -- which you can do easily in Haskell as well. JS doesn't give you anything Haskell doesn't there.
Its true that to make that a proper, statically-enforced type in Haskell takes some complexity, but if you aren't willing to do that, you could make it a runtime-enforced constraint on a statically-enforced type of integers as easily in Haskell as, in JavaScript, you could make it a runtime-enforced constraint on a value that is not statically enforced to be anything more specifically than a valid JS value.
I do not know Haskell enough either. But I didn't really mean 'using type system everywhere'. About your question, from what I've seen you can always go symbolic : define an abstract thing as twofourteenexceptten that represent that idea and use it. Or just use a filter everywhere to ensure that function requiring a 2and14but10 won't be called on something with other numbers. In that case you're not losing anything you'd do with a dynamic language.
What about dynamic language + runtime type checking + (nearly) 100% test coverage? You get the flexibility of a dynamic language and the safety of a static type language (in fact you get more, since runtime type checking is more powerful).