I was reading The Essence of Dependent Object Types and found the following encoding of Lists:
Why would one write:
nil: sci.List ∧ {A = ⊥}
in particular, why do we give A type bottom? Shouldn't the type be polymorphic like in cons?
Nothing is a bottom type is Scala - it is a type, which has no members, so you can easily say that each of its members is a member of every other type without lying.
On its own Nothing (as a return type) is used to say that function never returns value, which usually means that it throws exception. If you have any container/wrapper/factory/however you call if of Nothing, it means that it cannot contain a version of wrapper/whatever that contains/produces value:
List[Nothing] - is a List without any values,
Future[Nothing] - is a Future which runs in loop or ends up with exception
Option[Nothing] - is Option, that cannot contain a value
When it comes to List if you decide on using Cons+Nil as encoding, let's say you want to do it without any weird things:
sealed trait List[A]
case class Cons[A](a: head, tail: List[A]) extends List[A]
case class Nil[A]() extends List[A]
You cannot simply use object Nil which would have easier usage and pattern matching, because you have to define its type everywhere. So unfortunately you cannot have one Nil, but you need a separate Nil for every type.
Cons(1, Cons(2, Cons(3, Cons(4, Nil[Int]))))
But, it you made List[A] covariant then if A is subtype of B then List[A] would be subtype of List[B].
sealed trait List[+A] // notice + before A
case class Cons[+A](a: head, tail: List[A]) extends List[A]
case class Nil[+A]() extends List[A]
then we could make use of Nothing being a subtype of every other type:
val nil = Nil[Nothing]
Cons(1, Cons(2, Cons(3, Cons(4, nil))))
Cons("a", Cons("b", Cons("c", Cons("d", nil))))
at this point for our own convenience (e.g. pattern matching) we could make Nil an object:
sealed trait List[+A]
case class Cons[+A](a: head, tail: List[A]) extends List[A]
case object Nil extends List[Nothing]
Thanks to that we need only one Nil instead of one for every type.
That is how it works in the current Scala (2) and it hasn't changed in Dotty. DOT calculus in your example shows how this translates to formalism: instead of Nothing you have ⊥, everything else is basically the same but with a different notation.
Related
I'm using Circe and noticed something that i am not so confortable with and would like to understand what is going on under the hood ?
Fundamentally it is not really a circe issue. Also i was just playing with circe around to test few thing. So could have decoded in JsonObject straight but that is beside the point.
val jobjectStr = """{
| "idProperty": 1991264,
| "nIndex": 0,
| "sPropertyValue": "0165-5728"
| }""".stripMargin
val jobject = decode[Json](jobjectStr).flatMap{ json =>
json.as[JsonObject]
}
My issue is with the flapMap signature of Either, contravariance and what is happening here:
We have the following types:
decode[Json](jobjectStr): Either[Error, Json]
json.as[JsonObject]: Decoder.Result[JsonObject]
where circe defines
final type Result[A] = Either[DecodingFailure, A]
and
sealed abstract class DecodingFailure(val message: String) extends Error {
Now the signature of flatMap in either is:
def flatMap[A1 >: A, B1](f: B => Either[A1, B1]): Either[A1, B1]
In other words, talking only about type it is like my code is doing
Either[Error, Json] flatMap Either[DecodingFailure, JsonObject]
Hence my issue is: DecodingFailure >: Error is not true
And Indeed the type of the full expression is:
decode[Json](jobjectStr).flatMap{ json =>
json.as[JsonObject]
}: Either[Error, JsonObject]
Hence i'm confused, because my understanding is that the type of the first Parameter of Either is Contravariant in the flatMap Signature. Here there seems to be some wierd least upper bound inferencing going on ... But i am not sure why or if it is even the case.
Any explanation ?
This really isn't a variance issue. A1 >: A is just telling us that the result type, A1, might have to be a super-type of the received type, A, if the compiler has to go looking for a least upper bound (the LUB). (The use of A1 in the f: B => ... description is, I think, a bit confusing.)
Consider the following:
class Base
class SubA extends Base
class SubB extends Base
Either.cond(true, "a string", new SubA)
.flatMap(Either.cond(true, _, new SubB))
//res0: scala.util.Either[Base,String] = Right(a string)
Notice how the result is Either[Base,String] because Base is the LUB of SubA and SubB.
So first of all, we need to understand that the compiler will always try to infer types that allow compilation. The only real way to avoid something to compile is to use implicits.
(not sure if this is part of the language specification, or a compiler implementation detail, or something common to all compilers, or a bug or a feature).
Now, let's start with a simpler example List and ::.
sealed trait List[+A] {
def ::[B >: A](b: B): List[B] = Cons(b, this)
}
final case class Cons[+A](head: A, tail: List[A]) extends List[A]
final case object Nil extends List[Nothing]
So, assuming the compiler will always allow some code like x :: list will always compile. Then, we have three scenarios:
x is of type A and list is a List[A], so it is obvious that the returned value has to be of type List[A].
x is of some type C and list is a List[A], and C is a subtype of A (C <: A). Then, the compiler simply upcast x to be of type A and the process continues as the previous one.
x is of some type D and list is a List[A], and D is not a subtype of A. Then, the compiler finds a new type B which is the LUB between D and A, the compiler finally upcast both x to be of type B and list to be a List[B] (this is possible due covariance) and proceeds like the first one.
Also, note that due to the existence of types like Any and Nothing there is "always" a LUB between two types.
Now let's see Either and flatMap.
sealed trait Either[+L, +R] {
def flatMap[LL >: L, RR](f: R => Either[LL, RR]): Either[LL, RR]
}
final case class Left[+L](l: L) extends Either[L, Nothing]
final case clas Right[+R](r: R) extends Either[Nothing, R]
Now, assuming my left side is an Error, I feel this behaviour of returning the LUB between the two possible lefts is the best, since at the end I would have the first error, or the second error or the final value, so since I do not know which of the two errors it was then that error must be of some type that encapsulates both possible errors.
I am trying to match all binary operators in a single case clause, but the following code gives the error:
object BinOp is not a case class, nor does it have a valid unapply/unapplySeq member
Note: def unapply(a: AST.this.Expr, b: AST.this.Expr): Option[(AST.this.Expr, AST.this.Expr)] exists in object BinOp, but it cannot be used as an extractor as it has more than one (non-implicit) parameter.
Core code to traverse the tree:
tree match {
case ... other ... cases
case BinOp(a, b) => traverse(a), traverse(b)
}
The AST classes are as follows:
sealed trait Expr
case class Num(value: java.lang.Number) extends Expr
sealed trait BinOp extends Expr {
val a, b: Expr
}
object BinOp {
def unapply(a: Expr, b: Expr): Option[(Expr, Expr)] = Some(a, b)
}
case class Add(a: Expr, b: Expr) extends BinOp
case class Sub(a: Expr, b: Expr) extends BinOp
case class Mul(a: Expr, b: Expr) extends BinOp
case class Div(a: Expr, b: Expr) extends BinOp
The code segments are hugely simplified for illustration purposes.
The error message seems informative
def unapply(a: AST.this.Expr, b: AST.this.Expr): Option[(AST.this.Expr, AST.this.Expr)]
exists in object BinOp, but it cannot be used
as an extractor as it has more than one (non-implicit) parameter.
so unapply method should be defined with single parameter, say something like so
object BinOp {
def unapply(binOp: BinOp): Option[(Expr, Expr)] = Some(binOp.a, binOp.b)
}
It looks like you have a misconception about how extractor objects work. You can think of unapply as the dual of apply, although it isn't always so.
Suppose instead of having Add as a case class with the apply method already made for you, you made it an ordinary class and put the apply method in a companion object:
object Add {
def apply(a: Expr, b: Expr): Add = ???
}
The unapply method is just that, but with the input and output types switched (and inside an Option).
//Could also be a Some[(Expr,Expr)], as far as I can tell
def unapply(add: Add): Option[(Expr, Expr)] = (add.a, add.b)
Since the output of the apply method is just one value, the input of your unapply method is a single value, and since the apply method has multiple inputs, the unapply method has multiple outputs, or pretends to do so by using a tuple. There's a little more to it, but this page from the documentation will be in more in depth than I.
Edit: #jwvh has pointed out that you don't need to return an Option from an unapply method - it can be any type with the methods isEmpty and get.
Since you also mentioned that you want to abstract over any sort of node that contains other nodes, I'd like to point out that if you're going to use pattern matching, you'll still have to make your own unapply method for each sort of node (or make it a case class).
Given a type definition T <: Serializable, why doesn't this match everything that is serializable, including a List of serializable instances?
I.e. given:
case class Bar()
def foo[T <: Serializable](param1: T) = println(param1)
foo(Bar())
foo(List(Bar()))
The compiler gives the following error:
Error:(6, 2) inferred type arguments [List[A$A15.this.Bar]] do not conform to method foo's type parameter bounds [T <: Serializable]
The case class extends Serializable, so does List - why doesn't a list of Bars match the type?
The case class extends Serializable, so does List
This is true only for Scala 2.12.x (https://issues.scala-lang.org/browse/SI-7402). Prior versions (2.11 and 2.10) did not have the Serializable trait on List. If you use 2.12, your code will compile as expected.
The answer below takes into account the prior version implementation:
List[T] does not extend serializable, It's derives :: and Nil do. You can see this when explicitly using ::.apply
foo(::(1, Nil))
This compiles since the inferred type is :: (or Cons). On the contrary:
foo(1 :: Nil)
Does not because the :: method on List returns a List[A], not ::.
Another thing is that List.apply is always a runtime :: or Nil (because List is abstract), and that's the reason a test such as:
List(1,2,3).isInstanceOf[Serializable]
Yields true. If we check the underlying runtime class, we see:
List(1, 2, 3).getClass.getName
Yields
scala.collection.immutable.$colon$colon
I have following trait definition:
sealed trait List[+A]
// `List` data type, parameterized on a type, `A`
case object Nil extends List[Nothing]
// A `List` data constructor representing the empty list
/* Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`,
which may be `Nil` or another `Cons`.
*/
case class Cons[+A](head: A, tail: List[A]) extends List[A]
and a function:
def add1(l: List[Int]): List[Int] =
foldRight(l, Nil:List[Int])((h,t) => Cons(h+1,t))
My question is, what does Nil:List[Int] mean? Does it mean, I pass a Nil list with type with Int notation?
As fold (and it variants) is determining the type parameter based on the first parameter list, you cannot simply pass Nil, as the type would be derived as List[Nothing] (you would also see the second parameter list not matching). You can use type ascription to tell the Nil is of type List[Int]. You could also pass the List[Int] as the type parameter:
foldRight[List[Int]](l, Nil)((h,t) => Cons(h+1,t))
For the reference, foldRight signature is this:
def foldRight[B](z: B)(op: (A, B) => B): B
sealed trait List[+A] // `List` data type, parameterized on a type, `A`
case object Nil extends List[Nothing] // A `List` data constructor representing the empty list
/* Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`,
which may be `Nil` or another `Cons`.
*/
case class Cons[+A](head: A, tail: List[A]) extends List[A]
My question is what is the "+" in front of A?
Why in here "List[A]" the plus is ignored?
Thanks
A plus or minus sign in front of a type constructor argument means that values of that type appear in covariant (+) or contravariant (-) position. A covariant position means the type only ever occurs as "output" or return type, as is the case with List. Then a List[A] is a subtype of a List[B] if A <: B, if A is a sub-type of B:
trait Animal { def food: String }
case class Dog(name: String) extends Animal { def food = "all" }
def test(xs: List[Animal]) = xs.map(_.food)
test(List(Dog("Jussi")))
Here you can pass a List[Dog] for a List[Animal] because List[Dog] <: List[Animal].
Contravariance is the opposite - the type only occurs as input. For example Function1[A, Out] <: Function1[B, Out] if A >: B, if A is a super-type of B.
def test(fun: Dog => String): String = fun(Dog("Jussi"))
test { x: Animal => x.food }
Here you can pass a Animal => String for a Dog => String because the former is sub-type of the latter.
The variance annotation + or - only ever occurs in the definition of the type, so in the definition of List[+A], not anywhere else where List is used, e.g. as type ascription or in the extends clause, because the variance cannot change once it's defined. This is called definition-site variance.
Because Nothing is the bottom type in Scala, a type that is the sub-type of any other type, we can thus have the convenient object Nil extends List[Nothing], and thereby Nil becoming the sub-type of List[A] for any possible A. Whenever you need a list, no matter what the element type, you can use Nil.