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
Related
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.
I am reading Functional Programming in Scala from Manning, authored by Paul Chiusano and Runar Bjarnason. In its 3rd chapter, there is a code to create a List and there are assignments to implement various methods of the list. Following is partial implementation of the my List
package src.Cons
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](h:A, t:List[A]) extends List[A]
object List {
//my issue is I do not want to pass a list to sum but want to use objectName.sum notation
def sum(ints:List[Int]):Int = ints match {
case Nil => 0
case Cons(x,xs) => x+sum(xs)
}
}
Question - How can I create my list such that I can call l.sum instead of List.sum(l)?
You can "PmL", as #Gabriele Petronella has suggested, or you can move the sum() method to the Cons class, as #DeadNight wrote, but before either of those can work you have to resolve the current conflict between your List object and your List trait.
The sum() in your List object can only sum a List[Int] but your class definitions use a more generic type member and, as such, you can't use + because the compiler doesn't know how to add two A types.
If you want to restrict your List to only handling numeric types then this will work.
case class Cons[A: Numeric](h:A, t:List[A]) extends List[A] {
def sum: A = List.sum(this)
}
object List {
def sum[A](ints:List[A])(implicit ev: Numeric[A]):A = ints match {
case Nil => ev.zero
case Cons(x,xs) => ev.plus(x, sum(xs))
}
}
val x = Cons(4, Cons(2, Nil))
x.sum // res0: Int = 6
Making sum a member
The problem is, you don't know how to sum the List[A] for every type A, only a List[Int]. If there was a way to allow calls when A is an Int...
Let's take a look at the standard library for that. We're interested in Option#flatten method because:
val o1 = Option(Option(3)).flatten // compiles
val o2 = Option(4).flatten // does not compile
Notice the weird implicit ev: <:<[A, Option[B]]. This is the key here - it's a thing that compiler provides for you, but only if it is known at compile time, that your Option[A] is a subtype of Option[Option[B]] for some type B. This is the trick that we can use.
sealed trait List[+A] {
def sum(implicit ev: A <:< Int): Int = this match {
case Nil => 0
case Cons(x, xs) => x + xs.sum // <- here x is magically converted to Int, so we can use plus
}
}
case object Nil extends List[Nothing]
case class Cons[+A](h:A, t:List[A]) extends List[A]
println(Cons(4, Cons(38, Nil)).sum) // 42
ScalaFiddle
Notice that you can write <:<[A, B] as A <:< B.
NB: there's also =:=[A, B] type, for when your A is exactly Int - you can use either of those
Doing better?
Actually, std library has sum method and it's type is even weirder:
def sum(implicit ev: Numeric[A]). Doing so allows it to work on any number-like type like Double and Int, and has the operations for comparison, subtraction, multiplication, etc. So you can make it even more generic. I suggest you do it after reading a chapter about Monoids, tho :)
You can use the so-called "Pimp my Library" pattern.
Define an implicit class ListOps
implicit class ListOps[+A](list: List[A]) {
def sum = List.sum(this)
}
and now you can call list.sum. The implicit conversion will be triggered and the compiler will interpret it as ListOps(list).sum.
Move the definition of sum inside the definition of List trait
You can leave the concrete definitions to Nil & Cons
package src.Cons
sealed trait List[+A] {
def sum: Int
}
case object Nil extends List[Nothing] {
val sum: Int = 0
}
case class Cons[+A](h:A, t:List[A]) extends List[A] {
def sum: Int = h + t.sum
}
In the code below, why can't I call the sum function when I create an instance of fpinscala.datastructures.List? I.e. in the SBT console I do the following:
scala> :paste -raw exercises/src/main/scala/fpinscala/datastructures/List.scala
scala> val list = fpinscala.datastructures.List(2,3)
scala> list.sum(fpinscala.datastructures.List(2,3))
I guess my problem is I don't really understand the companion object - although my understanding was that it just defined functions on the type I have created which I could then call?
package fpinscala.datastructures
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]
object List { // `List` companion object. Contains functions for creating and working with lists.
def sum(ints: List[Int]): Int = ints match { // A function that uses pattern matching to add up a list of integers
case Nil => 0 // The sum of the empty list is 0.
case Cons(x,xs) => x + sum(xs) // The sum of a list starting with `x` is `x` plus the sum of the rest of the list.
}
def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) => x * product(xs)
}
def apply[A](as: A*): List[A] = // Variadic function syntax
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
}
EDIT: Maybe a better question is, how would I implement the following:
scala> val list = fpinscala.datastructures.List(2,3)
scala> list.sum (should return 5)
First, note that this line:
val list = fpinscala.datastructures.List(2,3)
is the same as the following:
val list = fpinscala.datastructures.List.apply(2,3)
In other words, you are calling the method apply in the List object. The return type of the apply method is the trait List, so the type of the val list is the trait List.
This means that when you call list.sum, you're trying to call the sum method of the trait List. But your trait List does not have a sum method, so it fails.
You have put the sum method in the companion object of trait List - you should put it in the trait instead (removing the parameter; or call the sum in the List object, passing list as the argument, as Seth Tisue noted in his comment).
A trait or class does not automatically have all the methods of its companion object.
Answering question about how to implement list.sum: you should define method in your List trait. Take a look at scala.collection.TraversableOnce 'sum' implementation:
def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)
So far List has arbitrary type, implicit instance of Numeric monoid is required - it gives zero value and operation (plus in the case of sum)
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.
Looking at the source for List.scala:
sealed abstract class List[+A] extends ...
...
def isEmpty: Boolean
def head: A
def tail: List[A]
List[+A] is covariant based on the +A. Does this mean that, it's possible to create a List[T] where T can be the type itself, or any of its sub-classes?
example:
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> case class Girl(name: String) extends Kid
defined class Girl
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
Observe that head and tail's types are A and List[A], respectively. Once we've defined List[+A], then head and tail's A is also covariant?
I've read this StackOverflow answer 3 or 4 times, but I don't understand yet.
Your example does not relate to variance. Moreover, head and tail have nothing to do with variance too.
scala> val list: List[Kid] = List(Boy("kevin"), Girl("sally"))
list: List[Kid] = List(Boy(kevin), Girl(sally))
This would work even if List weren't covariant, because Scala will automatically deduce common supertype of Boy and Girl, that is, Kid, and type of the expression on the right side will be List[Kid], exactly what you require on the left side.
The following, however, doesn't work because java.util.List is not covariant (it is invariant since it is Java type):
scala> import java.util.{List => JList, Arrays}
import java.util.{List=>JList, Arrays}
scala> trait Kid
defined trait Kid
scala> case class Boy(name: String) extends Kid
defined class Boy
scala> val list1 = Arrays.asList(Boy("kevin"), Boy("bob"))
list1: java.util.List[Boy] = [Boy(kevin), Boy(bob)]
scala> val list2: JList[Kid] = list1
<console>:12: error: type mismatch;
found : java.util.List[Boy]
required: java.util.List[Kid]
Note: Boy <: Kid, but Java-defined trait List is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Kid`. (SLS 3.2.10)
val list2: JList[Kid] = list1
^
Arrays.asList method has signature like this:
def asList[T](args: T*): java.util.List[T]
As java.util.List[T] is invariant, it is impossible to assign JList[Boy] (list1) to JList[Kid] (list2). And there is a reason: if you could, then because JList is mutable, you could also add anything extending Kid (not only Boy) into the same list, breaking type safety.
On the other hand, scala.List will work in exactly the same situation:
scala> val list1 = List(Boy("kevin"), Boy("bob"))
list1: List[Boy] = List(Boy(kevin), Boy(bob))
scala> val list2: List[Kid] = list1
list2: List[Kid] = List(Boy(kevin), Boy(bob))
That is because scala.List is covariant in its type parameter. Note that covariant List type works as if List[Boy] were subtype of List[Kid], very similar to the case when you can assign everything to a variable of type Any because every other type is a subtype of Any. This is very helpful analogy.
Contravariance works in a very similar way, but in other direction. Consider this trait:
trait Predicate[-T] {
def apply(obj: T): Boolean
}
object Predicate {
// convenience method to convert functions to predicates
def apply[T](f: (T) => Boolean) = new Predicate[T] {
def apply(obj: T) = f(obj)
}
}
Note the - before T parameter: it is a contravariance annotation, that is, Predicate[T] is defined to be contravariant in its only type parameter.
Recall that for covariant list List[Boy] was a subtype of List[Kid]. Well, for contravariant predicate it works in the opposite way: Predicate[Kid] is a subtype of Predicate[Boy], so you can assign a value of type Predicate[Kid] to a variable of type Predicate[Boy]:
scala> val pred1: Predicate[Kid] = Predicate { kid => kid.hashCode % 2 == 0 }
pred1: Predicate[Kid] = Predicate$$anon$1#3bccdcdd
scala> val pred2: Predicate[Boy] = pred1
pred2: Predicate[Boy] = Predicate$$anon$1#3bccdcdd
If Predicate[T] weren't contravariant, we wouldn't be able to assign pred1 to pred2, though it is completely legitimate and safe: obviously, predicates defined on supertypes can easily work on subtypes.
In short, variance affects type compatibility between parameterized types. List is covariant, so you can assign a value of type List[Boy] to a variable of type List[Kid] (in fact, for any T extending S, you can assign a value of type List[T] to a variable of type List[S]).
On the other hand, because, Predicate is contravariant, you can assign Predicate[Kid] to Predicate[Boy] (that is, for any T extending S, you can assign a value of type Predicate[S] to a variable of type Predicate[T]).
If a type is invariant in its type parameter, neither of the above can be done (as is demonstrated by JList).
Note the correspondence between parameterized types and their parameters:
T <: S ===> List [T] <: List [S] (covariance)
T <: S ===> Predicate[S] <: Predicate[T] (contravariance)
This is the reason why the first effect is called *co*variance (T <: S on the left, and
..T.. <: ..S.. on the right), and the second is *contra*variance (T <: S on the left, but ..S.. <: ..T.. on the right).
Whether to make your own parameterized types covariant or contravariant or invariant depends on your class responsibilities. If it may only return values of generic type, then it makes sense to use covariance. List[T], for example, only contains methods which return T, never accept T as a parameter, so it is safe to make it covariant in order to increase expressiveness. Such parameterized types can be called producers.
If your class only accepts values of the generic type as a parameter, not returns them (exactly like Predicate above which has single method def apply(obj: T): Boolean), then you can safely make it contravariant. Such parameterized types can be called consumers
If your class both accepts and returns values of the generic type, i.e. it is both a producer and a consumer, then you have no choice but to leave the class invariant in this generic type parameter.
This idiom is usually called "PECS" ("Producer extends, Consumer super") because variance annotations are written extends and super in Java.