// type annotation
val i: Int = 0 // value i = 0 is of type Int
val s: String = "a" // value s = "a" is of type String
def double(x: Int): Int = x * 2
// type ascription
0 : Int // compiles because it is true
"": Int // fails to compileMateusz Kubuszok
Introduction to TypesA collection of values
// type annotation
val i: Int = 0 // value i = 0 is of type Int
val s: String = "a" // value s = "a" is of type String
def double(x: Int): Int = x * 2
// type ascription
0 : Int // compiles because it is true
"": Int // fails to compiletrait User
class Member extends User
class Admin extends Userclass User
class Admin extends Userval i: Int
val x = if (i >= 0) Some(i) // Some[Int]
else None // None
// what is the type of x?Some(i)NoneLeast Upper Bound of a - the least type T for which judgement a : T is provable to be true
Type Inference - finding the LUB of a value
val i: Int
val x = if (i >= 0) Some(i) // Some[Int]
else None // None
// x: Option[Int]ADT is either a product or coproduct type. :)
But what are product and coproduct types?
A tuple or an ordered pair, is a collection of two elements, where we select one of them as the first.
In set theory we can define them as:
Generalization of Cartesian product:
type X = (String, Int, Double)
type Y = Tuple3[String, Int, Double]
case class Z(s: String, i: Int, d: Double)
class Z2(val s: String, val i: Int, val d: Double)
import shapeless._
String :: Int :: Double :: HNilsealed trait Credentials
final case class LoginPassword(
login: String,
password: String
) extends Credentials
final case class AccessToken(
token: String
) extends Credentialstype My = String | IntIn set theory we have set intersection.
What do we have in Scala type system?
trait Str { def str: String }
trait Count { def count: Int }
def repeat(cd: Str with Count): String =
Iterator.fill(cd.count)(cd.str).mkString
repeat(new Str with Count {
val str = "test"
val count = 3
})val sc: Str with Count
val ca: Count with Str
def repeat(sc) // works as expected
def repeat(ca) // also works!trait A { def value = 10 }
trait B extends A { override def value = super.value * 2 }
trait C extends A { override def value = super.value + 2 }
(new B with C {}).value // ???
(new C with B {}).value // ???trait X extends A with B with Cis the same as
trait AnonymousB extends A {
// B overrides A
override def value = super.value * 2
}
trait AnonymousC extends AnonymousB {
// C overrides AnonymousB
override def value = super.value + 2
}
trait X extends AnonymousCtype My = String & IntMathematically:
A class is such group of objects for which some predicate (an indicator function) returns true.
Programming:
A recipe for objects + contracts. Instances of that class can be a type.
(): UnitIf we have a concrete type - e.g. String - we know it is a set of values.
What about List?
type - e.g. Int - set of values - e.g. 1, 2, 0, -5, …
function - e.g. Int ⇒ Int - set of pairs (Int, Int), where first value doesn’t repeat - e.g. (1,1,), (2,4), (3,9), …
we can make a pair of sets (types),
function can take set (type) as an argument and return set (type) as a value
// [A] declares type parameter A
class Wrapper[A](value: A)
val wrapped1 = new Wrapper[Int](1)
// Wrapper[Int] - Int passed explicitly
val wrapped2 = new Wrapper(2)
// Wrapper[Int] - Int inferredExamples: Option[A], List[A], Either[L, R].
a type is a set of values
a subset, a product set, a set sum and intersection translates to a subtype, a product type, a sum type and an compound/intersection types
a class is a type
unit exist to avoid special cases
on mathematical level a parametric type is a function from type to type
All you need to know about types in ScalaA type of a type :)
sealed trait User { val id: Int }
case class Member(id: Int, name: String) extends User
case class Admin(id: Int, accss: Set[String]) extends UserMap[id, user] - approach 1
def byId(users: Set[User]): Map[Int, User] =
users.map { u => u.id -> u }.toMapMap[id, user] - approach 2
def byId[U](users: Set[U])(getId: U => Int): Map[Int, U] =
users.map { u => getId(u) -> u }.toMapsealed trait User { val id: Int }
case class Member(id: Int, name: String) extends User
case class Admin(id: Int, accss: Set[String]) extends UserMap[id, user] - approach 1
def byId[U <: User](users: Set[U]): Map[Int, U] =
users.map { u => u.id -> u }.toMapMap[id, user] - approach 2
byId(users: Set[Member]) // Map[Int, Member]
byId(users: Set[Admin]) // Map[Int, Admin]def recover[E, A, B >: A](
either: Either[E, A])(f: E => B): Either[E, B] =
either match {
case Left(e) => Right(f(e))
case Right(a) => Right(a)
}recover[String, Admin, User](err: Either[String, Admin]) {
_ =>
fallback: Member
}
// Either[String, User]def upcast[A, B](set: Set[A])(
implicit ev: A <:< B // A is a subclass of B
): Set[B] = set.map(ev(_))
upcast[Member, User](Set(m: Member)) // Set[User]def update[A, B](set: Set[A])(f: A => B)(
implicit ev: A =:= B // types are equal
): Set[B] = set.map(f)
val members: Set[Member]
update[Member, Member](members)(identity) // ok
update[Member, User](members) { member =>
member: User
} // compilation error!<%< - already removed from Scala, meant A is a suptype is is implicitly convertible to B,
=:!= - types differ - provided by Shapeless
trait X[F[_]]
def needX[F[_] : X] = ??? // is equal to
def needX[F[_]](implicit xf: X[F]) = ???String[] strings = new String[3];
strings[0] = "1";
strings[1] = "2"; // ok so far
Object[] objects = strings; // we can do that as well
objects[2] = (Integer) 3;
// java.lang.ArrayStoreException: java.lang.IntegerList<String> strings = new ArrayList<String>();
strings.add(0, "1");
strings.add(1, "2");
List<Object> objects = strings; // compilation errorSituation where:
is called invariance.
sealed trait Option[T] {}
case class Some[T](value: T) extends Option[T]
case class None[T]() extends Option[T]class A
class B extends A
val o: Option[B] = Some(new B)
def withOptA(opt: Option[A]) = ???
withOptA(o) // doesn't work
None[A]() != None[B]() // doesn't make senseSituation where:
is called covariance.
sealed trait Option[+T] {} // + makes the difference
case class Some[+T](value: T) extends Option[T]
object None extends Option[Nothing]class A
class B extends A
val o: Option[B] = Some(new B)
def withOptA(opt: Option[A]) = ???
withOptA(o) // compiles
(None: Option[A]) == (None: Option[B]) // truetrait Subscriber[A] {
def apply(value: A): Unit
}class A
class B extends A
val subscriberA: Subscriber[A]
List(new B).foreach(subscriberA) // compilation fails!Situation where:
is called contravariance.
trait Subscriber[-A] { // - makes the difference
def apply(value: A): Unit
}class A
class B extends A
val subscriberA: Subscriber[A]
List(new B).foreach(subscriberA) // works!Variances - invariance, covariance, contravariance - is related to type parameter, not the whole type.
trait Function1[-A, +B] {
def apply(arg: A): B
}val function: Function1[A, B]
def b2b(f: Function[B, B]): Unit
def a2a(f: Function[A, A]): Unit
b2b(function) // accepting more generic argument is ok
a2a(function) // returning more specific result is okdef count[T](seqs: Seq[T]*): Int = seqs.map(_.size).sumcount(Seq(1,2,3), Seq("test")) // 4Universal type
Existential type
def count(seqs: Seq[_]*): Int // syntactic sugar for
def count(seqs: (Seq[T] forSome { type T })*): IntJava also has them!
int count(java.util.List<?>... seqs) {
return Arrays.stream(seqs)
.mapToInt(seq -> seq.size())
.sum();
}type User = { name: string, surname: string }type User = {
val name: String
val surname: String
}case class Somebody(name: String, surname: String)
def needUser(user: User): Unit
needUser(Somebody("test", "test")) // works!trait X { val x: String }
type Y = { val y: Int }
val z: X with Y = new X { val x = "test"; val y = 0 }new X { val x = "test"; val y = 0 }
// AnyRef with X{val y: Int}case class Card(color: String, value: String)
case class Player(name: String)
class Game {
def getPlayers: Set[Players] = ???
def getCards: Set[Cards] = ???
def playerPlayCard(player: Player, card: Card): Unit = ???
}val game1: Game
val game2: Game
game1.playerPlayCard(game2.getPlayers.head,
game2.getCards.head) // oops!class Game {
case class Card(color: String, value: String)
case class Player(name: String)
def getPlayers: Set[this.Player] = ???
def getCards: Set[this.Card] = ???
def playerPlayCard(player: this.Player,
card: this.Card): Unit = ???
}val game1 = new Game
val game2 = new Game
game1.getPlayers // Set[game1.Player]
game2.getPlayers // Set[game2.Player]game1.playerPlayCard(game2.getPlayers.head,
game2.getCards.head) // fails!class X {
type Y = String
val y: Y = "y"
}
val x1 = new X
val x2 = new Xdef y(x: X)(y: x.Y): Unit = ()
y(x1)(x2.y) // no complaints: x1.Y = String = x2.Ytrait X {
type Y = String
val y: Y = "y"
}
class X2 extends X
val x1 = new X2
val x2 = new X2
y(x1)(x2.y) // fails!trait X {
type Y
val y: Y
}
val x1 = new X {
type Y = String
val y: Y = "y"
}
val x2 = new X {
type Y = Int
val y: Y = 1
}def takeAnyPlayer(p: Game#Player): Unittype WithFallback[A] = Either[String, A]
def fallbackOnError[A: WithFallback](
value: Either[String, A]
): Either[String, A] = value match {
case Left(_) => implicitly[Either[String, A]]
case Right(value) => Right(value)
}type WithFallback = { type T[A] = Either[String, A] }
def fallbackOnError[A: WithFallback#T](
value: Either[String, A]
): Either[String, A] = ...def fallbackOnError[A: ({ type T[A] = Either[String, A] })#T](
value: Either[String, A]
): Either[String, A]def fallbackOnError[A: Either[String, ?]](
value: Either[String, A]
): Either[String, A][A][T] => Either[String, T]-Ypartial-unificationdef foo[F[_], A](fa: F[A]) = fa.toString
foo[Function1[Int], Int](x => x * 2) // error!// build.sbt
scalacOptions += "-Ypartial-unification"we have a quite fine grained control over type parameters and their usage
Scala does it best to preserve the type information even if we don’t need it (now)
you (and surely some library authors) will use path-dependent types to ensure that some values will be passed only in the right context
Mateusz Kubuszok
Tweeter: @MateuszKubuszok
GitHub: @MateuszKubuszok