Scala’s Types of Types
ktoso.github.io
ktoso.github.io
Only arrays are variant "by default" in java (which was a huge mistake). Variance for generic collections is specified at use, not at the type definition level.
> This means that if you have a container defined as Box[A], and then use it with a Fruit in place of the type parameter A, you will not be able to insert an Apple (which IS-A Fruit) into it.
No. What this really means is that you can't assign an instance of Box[Apple] to a val/var of type Box[Fruit].
List<Fruit> fruits = apples
Then you could add an orange to the fruits list, and it would appear among the apples.
It doesn't seem like you can fix this without immutability?
A type constructor of lists (which Java's standard library doesn't have to the best of my knowledge) is covariant on its element type, but a type constructor of mutable cells is invariant on its content type.
List<Apple> apples = aps
If you're using a general type Fruit, and can't stand any fruit that isn't an apple, you probably need to use the more specific Apple type.
The assignment sort of goes both ways like that.
If bottom type corresponds to no(thing) and top to any(thing) where does every(thing) and some(thing) fit in? Is there any semantic distinction between any(thing) and every(thing)? There should be, right? For me, some(thing) that had type any(thing) would be an _individual_ but some(thing) that had type every(thing) {actually the mind boggles a bit here} would be a _colection_, the collection of all existing or living things. In fact Ruby has the notion of Objectspace. So we can say things like ObjectSpace.each_object{|x| p x}
What would an "everything" type be? I'd guess the "and" of all your types, meaning "any value that belongs to all (non-bottom?) types" (e.g. an imaginary 0 symbol meaning zero float, empty list, empty string according to the context). If you want your "every" type to consider bottom type, then the intersection is empty.
Likewise, the "some" type would be a type containing values that belong to at least one type, which IMO looks like the "any" type.
More specifically, we've lost any information to tell us what specific thing might be here so we have to be prepared for any answer. It's the inverse of (forall a . a) which we can ask to become whatever we like.
(edit: to be clear, the bottom type, aka zero/empty type).
It does not means that it contains the values that all types contain. Rather, it contains the values that belong to every type. In other words, in order to belong to the type "Everything," a value has to simultaneously belong to every other type.
One way to think about this is that the name applies to each member of the type, not to the type as a whole. "Anything" is an accurate description of any possible value, so the set contains all values. "Everything" does not apply to the entire set, but to each individual element. An element must be everything all at once in order for us to describe it as "Everything." Nothing does that, so "Nothing" does that.
Avoid natural language like the plague.
foo = Sheep.new => a sheep
foo.is_a(Any) => true (for anything! by the nature of Any)
I agree that "some" looks like "any". I wonder is there any (ahem) semantic difference in the context we are considering.I can see why you would say that (every)thing is the "and" of all my types. But consider that I think it obvious that (every)thing should cover all things, this would include instantiations of types as well as the types themselves.
Programming environment-wise I'm having a hard time imagining how "everything" would work. It must be related to for_all or universal quantification somehow...
It may be worth taking a moment to skip out on the abstraction and look more at the representation layer. In the general case, in most if not all implementations, "Any" basically translates to a "Variant" type: https://en.wikipedia.org/wiki/Variant_type
"Dynamically-typed languages" are basically those languages where all variables have the type Any. (The values generally have specific types, but the variables are all Any.)
Types are there to describe the values that can be manipulated by your program. So "as well as the types themselves" is only a concern if your language can manipulate types as first-class values. And this is not so different from all the other types in your language, at this point.
What property should a value hold to belong to the "everything" type, then?
If you want an Everything type that is different from the Any type, the only type I can think of would be a type whose instances behave the same as every type. That doesn't make a lot sense to me.
I know, right?
For me, everything semantically would have to include all types and all instances of types. Types are things also after all. In type theory collections of types are called universes. I'm not referring to those (I think). Everything encompasses all things, not just all types. Also, would everything include itself? Giving us Russell's paradox, which type theory seeks to avoid.
We ought to be able to talk about all things. And everything is an alias for the phrase "all things".
I agree that Any is kind of (exactly) like void* in C or BasicObject in Ruby.
To sort-of approach your questions, ideas that you might find interesting are an unbound existential type T forSome { type T }, and also the fact that Nothing <: T for any type T (this is the principle of explosion in logic)
Why isn't the type of this expression Null?
You'd have different compilers determining different types based on optimisation which are supposed to be transparent. Doesn't seem desirable to me.
From my understanding DOT could turn out to be quite similar to this pure subtype approach http://lambda-the-ultimate.org/node/4835 so isn't that farfetched either.
In this case it is undesirable for the compiler/model to change the static type since that may then let you make other changes which you should'nt be making, or cause compiler errors further down.
Right now, the typing rule for if in Scala is reasonably simple. The condition is a boolean and the whole expression is the least upper bound of the left branch and the right branch. How much more complex would this rule have to be if it needed to fully specify when the condition gets simplified?
To be sure, your idea is interesting and could probably be done well in another language. I wouldn't be surprised if this sort of capability naturally comes up in some dependently typed systems. But it wouldn't work very well in Scala: you'd be adding a lot of complexity for relatively little gain.
I was surprised because I though it is quite a standard analysis in compilers nowadays. I had in mind SBCL precise type-checking (Common Lisp). Consider for example:
(describe (lambda (x) (if nil x nil)))
#<FUNCTION (LAMBDA (X)) {1005F6E9FB}>
[compiled function]
Lambda-list: (X)
Derived type: (FUNCTION (T) (VALUES NULL &OPTIONAL))
The derived type is NULL (which contains only NIL).
And, more importantly, the compiler signals the presence of dead code, which is helpful for finding typos. It can handle more complex situations where the mistake is not obvious.
The type system supports interval types like "(integer low high)": (describe (lambda (x) (if x 3 2)))
#<FUNCTION (LAMBDA (X)) {100613759B}>
[compiled function]
Lambda-list: (X)
Derived type: (FUNCTION (T) (VALUES (INTEGER 2 3) &OPTIONAL))
As well as set operations on types: (describe (lambda (x) (if x 2 4)))
#<FUNCTION (LAMBDA (X)) {10062F5C8B}>
[compiled function]
Lambda-list: (X)
Derived type: (FUNCTION (T)
(VALUES (OR (INTEGER 2 2) (INTEGER 4 4)) &OPTIONAL))
So it is definitely doable, and even though it is still limited, it is less limited and quite useful.But the code is robust and you can give a string to a function that is declared to accept a number (e.g. from the REPL): your program won't crash, because types are checked at runtime too (you have an exception).
So, with default safety and speed optimization levels, adding type declarations will actually add runtime checks. But if you say "trust me, this will be a fixnum, produce some efficient code", then the type discrimination that would happen at runtime is bypassed and you directly emit the opcodes related to your type and this is how you optimize your code. For example, requesting a high-level of speed and low safety/debug will report a lot of optimization notes from SBCL, like: "forced to do GENERIC-+ instead of FIXNUM-+ because of type uncertainty".
http://www.lispforum.com/viewtopic.php?f=2&t=191
http://www.xach.com/sbcl/doc/x282.html
http://www.pvk.ca/Blog/2013/11/22/the-weaknesses-of-sbcls-ty...
- (0 < 1) is a constant.
- ((10 + 10)*15 > 0) is a constant.
where do you draw the line in the type checker at working these things out? [[syntax trees at end of typer]] // constif.scala
class ConstIf extends scala.AnyRef {
def m: Any = if (false{Boolean(false)})
23{Int(23)}
else
null{Null(null)}{Any}
}
[[syntax trees at end of refchecks]] // constif.scala
class ConstIf extends scala.AnyRef {
def m: Any = null{Null(null)}
}I think you can actually have stuff like this make sense in dependent type systems, though.