What is Covariance and Contravariance Anyways?
functional-orbitz.blogspot.com
functional-orbitz.blogspot.com
"A compile-time check has been moved to runtime, which means you don't know if your code is safe without running it."
In an optionally-typed language like Dart, this is always true. Dart doesn't require enough type information to allow the compiler to guarantee that code that passes the type checker won't throw a runtime exception. So whether a feature undermines type soundness is irrelevant. The important question is how big is the class of errors it fails to catch at compile-time versus what other benefits does it have.
"Unfortunately, I cannot shed any light on why Dart has covariant arrays"
Because covariant arrays are more consistent with programmer intent and in an optionally typed language don't appreciably undermine compile-time guarantees more than the "correct" rule. Good code avoids mutating arrays. Hence a rule that is correct for the common case of reading arrays makes sense. Moreover, such a rule keeps you from having to lie:
Given: Basket<Tomatos> HarvestTomatos()
MakeJuice(Basket<Fruit>)
MakeJuice(HarvestTomatos()) should Just Work (TM). The optional typing keeps me from accidentally passing MakeJuice a Basket<Insects> which is, as a dynamic language programmer, all I really want from the type system.
So, e.g., HarvestTomatoes() can return a Basket<Tomato>, which is shorthand for Basket<emits:Tomato,accepts:Tomato>, and MakeJuice can take a Basket<emits:Fruit,accepts:EMPTY_TYPE>, and it's safe to feed the former into something that wants the latter.
(Of course actual container objects will typically be of type Container<emits:T,accepts:T> for some specific T.)
MakeJuice :: Basket<`a> -> void where `a <: Fruit
That is, of course, only if MakeJuice does not modify the array. In general, I think that often the biggest mistake is variance annotation of types. Instead, functions should have (in)variance annotation on every parameter and on the return value. Then, a function that only reads from an array would be covariant in the type of array elements, a function that only writes would be contravariant, and a function that does both would be invariant. No need to artificially limit the usecases of arrays.
You can pass an Apple[] to something accepting a Fruit[] and get a runtime exception when you try to add a Banana to the Fruit[].
Naturally, SML does as well. SML also has immutable lists, which are covariant.
For languages that implement generics in a way that takes variance into account, you can look at e.g. Java (ignore the array class, but look at Vector<E>) or Scala. OCaml also has subtyping (for records and variants) and variance-annotated type variables (http://ocaml.janestreet.com/?q=node/99).
Num looks like something that's been subtyped.
Num is a typeclass, not a type. Like yummyfajitas said, typeclasses are similar to interfaces or abstract classes in Java, but they aren't the same.
Consider Java's equals(). You can write 'a'.equals(foo) as long as foo is an Object.
In contrast, look at Haskell's (==) function declared in Eq (I'm not giving an example using Num because Haskell provides some implicit conversions for numeric literals, which would introduce an unrelated complication in the example).
(==) :: (Eq a) => a -> a -> Bool
This functions accepts 2 arguments of any one type, as long that type is an instance of Eq. But we cannot use different types in the same call. That is, we can write ('a' == 'b') and ("foo" == "bar") but not ('a' = "foo"), because (==) is not defined for a Char and a String. applyShippingCost(num price) {
return price + 10;
}
main() {
print(applyShippingCost(100)); // Prints 110. Ok.
var lol = "TROLOLO";
num thisIsNotANum = applyShippingCost(lol); // Nothing explodes here...
print(thisIsNotANum); // Prints TROLOLO10, Yeahh B)
}
This runs well and compiles with no warnings. Types are not checked when assigning an untyped variable to a typed one, so while one would expect that type declarations should give us some form of type safety, they don't.As I see it, with Dart's optionally typed system you get the worst part of two worlds (if you opt to type your variables): having to declare the types of variables, as in a static type system (with no compiler type inference); and having no guarantee about those type declarations at runtime, as in a duck-typing type system.
That is ultimately the point of optional typing. To let you gradually firm up the types in your program as the design gels. The technology is there to get quite a lot of mileage out of the resulting type information, though I think it would definitely behoove Google to make sure the tooling works well from 1.0.
E.g: https://gist.github.com/1325465
In the declaim I've specified the type of fib to be integer => integer. In the test function, I define a local function that calls a library function to concatenate two constants. The test function then calls fib with the result of calling the local function.
Compiling this, SBCL complains: https://gist.github.com/1325469
Here, the compiler starts with the known types of "hello" and " world", realizes that the result of CONCATENATE must be an array or null (strings are arrays in Common Lisp), realize that the function FUN always returns a sequence, and then realize that this result conflicts with the asserted type of FIB.
Indeed, if you define FIB inside TEST[1] you don't even need to declare the type of FIB, SBCL figures it out itself.
[1] This restriction is a limitation of the semantics of Common Lisp. Global definitions could be redefined, so SBCL will not flow types through them. Lexical definitions cannot be redefined, so SBCL will analyze them. Dart does not have this limitation.