Деньгин Роман
Классический пример того, почему нам нужны параметры типов:
case class IntBox(value: Int)
case class StringBox(value: String)
case class BooleanBox(value: Boolean)
val intBox = IntBox(1)
val stringBox = StringBox("Hello")
val booleanBox = BooleanBox(true)
case class Cat(name: String)
val catBox = ???
// здесь T - это параметр типа
case class Box[T](value: T)
val intBox1 = Box[Int](1)
val intBox2 = Box(2)
val stringBox = Box("Hello")
val booleanBox = Box(true)
case class Cat(name: String)
val catBox = Box[Cat](Cat("Boris"))
// Для типов можно использовать несколько параметров
case class Box2[A, B](first: A, second: B)
val box2: Box2[Int, Boolean] = Box2(12, false)
// Box и Box2 не являются типами
val justBox = Box // error: class Box takes type parameters
Box и Box2 являются конструкторами типов.
К примеру, в Box необходимо передать тип, чтобы сконструировать конкретный тип.
Конкретный тип: *
Int
String
Box[Int]
Box[Box[String]]
List[Double]
Box2[Double, String]
Map[String, Int]
Конструктор типа с одним параметром: * -> *
Box
List
Option
Box2[Int, *]
Map[*, Double]
Конструктор типа с двумя параметрами: * -> * -> *
Box2
Map
Either
Здесь * -> * -> * можно воспринимать, как некоторую функцию от n параметров, которая принимает
тип и возвращает функцию от (n - 1)-го параметра, пока не получится конкретный тип.
(* -> *) -> *
Конструктор типа, который принимает в качестве аргумента другой конструктор типа.
Чтобы разрешить подобное поведение к компиляютору добавляется специальный флаг:
-language:higherKinds
case class HigherKindedBox[T[_]](
value: T[Int]
)
case class HigherKindedBox2[T[_], A](
value: T[A]
)
val boxWithIntList = HigherKindedBox2[List, Int](value = List(1, 2, 3))
Как игнорировать параметр типа
case class Box2[A, B](first: A, second: B)
def getFirst[T](box: Box2[T, _]): T = box.first
val first: String = getFirst(Box2("Hello", 42))
Рассмотрим следующий пример:
case class HigherKindedBox[T[_]](value: T[Int])
// List - конструктор типа от одного параметра, потому никакой проблемы тут нет
val ok = HigherKindedBox(List(1, 2, 3))
// Map - конструктор типа от двух параметров, а T[_] требует конструктор с одним параметром
val error = HigherKindedBox(Map(1 -> "a", 2 -> "b")
Что мы можем сделать, чтобы Map хоть в каком-то формате уместить в HigherKindedBox:
case class HigherKindedBox[T[_]](value: T[Int])
// Можем заранее определить новый тип, который будет являться алиасом для типа Map,
// у которого один из параметров типа уже заполнен
type SingleMap[A] = Map[Int, A]
val ok = HigherKindedBox[SingleMap[String]](Map(1 -> "a", 2 -> "b"))
// Или можем прямо по месту использования сделать так:
val ok2 = HigherKindedBox[Map[Int, *]](Map(1 -> "a", 2 -> "b"))
Здесь Map[Int, *] - это частично унифицированный конструктор типа.
Чтобы такое поведение стало доступным к компилятору нужно добавить флаг:
-Ypartial-unification
Мы можем параметризовать параметры функций
def toBox2[A, B](
first: Box[A],
second: Box[B]
): Box2[A, B] = Box2(first.value, second.value)
// Можем в определённых местах указывать конкретные типы
def toBox2[A](
first: Box[A],
second: Box[Int]
): Box2[A, Int] = Box2(first.value, second.value)
// повторимся про возможность игнорировать определённые типы
def getFirst[T](box: Box2[T, _]): T = box.first
UPPER BOUNDS
trait Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
val cats = NotEmptyList.of(Cat("Барсик"), Cat("Кеша"))
val dogs = NotEmptyList.of(Dog("Шарик"), Dog("Барбос"))
def shortestName(animals: NotEmptyList[Animal]): Animal =
animals.sortBy(_.name.length).head
def sayMeow(cat: Cat): String = s"${cat.name} says meow."
sayMeow(shortestName(cats)) // error: type mismatch; found: Animal, required: Cat
UPPER BOUNDS
trait Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
val cats = NotEmptyList.of(Cat("Барсик"), Cat("Кеша"))
val dogs = NotEmptyList.of(Dog("Шарик"), Dog("Барбос"))
def shortestName[T <: Animal](
animals: NotEmptyList[T]
): T = animals.sortBy(_.name.length).head
def sayMeow(cat: Cat): String = s"${cat.name} says meow."
sayMeow(shortestName(cats)) // ok
Запись A <: B означает, что тип А является самим типом B или его потомком.
LOWER BOUNDS
trait Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
val cats = NotEmptyList.of(Cat("Барсик"), Cat("Кеша"))
def sayMeow(cat: Cat): String = s"${cat.name} says meow."
def addToCats(
cats: List[Cat],
other: List[Animal]
): List[Animal] = cats ++ other
addToCats(cats, cats).map { cat => sayMeow(cat) } // error: type mismatch
LOWER BOUNDS
trait Animal {
def name: String
}
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
val cats = NotEmptyList.of(Cat("Барсик"), Cat("Кеша"))
def sayMeow(cat: Cat): String = s"${cat.name} says meow."
def addToCats[T >: Cat](
cats: List[Cat],
other: List[T]
): List[T] = cats ++ other
addToCats(cats, cats).map { cat => sayMeow(cat) } // ok
Пока отвлечёмся на от ограничений на типы и поговорим про ключевое слово implicit.
Для значений, функций, классов, помеченных implicit, существует отдельный implicit scope, из которого
компилятор пытается находить необходимые ему данные.
// неявная передача параметра в функцию
implicit val implicitInt = 5
def implicitSum(x: Int)(implicit y: Int): Int = x + y
println(implicitSup(5)) // 10
// implicit conversion
case class User(name: String, age: Int)
implicit def userToInt(user: User): Int = user.age
println(5 + User("Max", 5")) // 10
Про области видимости неявных значений, функций, классов.
Сначала производим поиск в локальной области видимости, включая импортированные implicits.
Потом происходит поиск в объекте-компаньоне типа.
В последний очередь поиск происходит в объекте-компаньоне супертипа, если было наследование.
trait ToJson[A] {
def toJson(value: A): Json
}
case class User(name: String, age: Int)
object User {
implicit val userToJson: ToJson[User] = new ToJson[User] {
def toJson(value: User): Json = ???
}
}
def printJson[T](value: T)(implicit toJson: ToJson[T]): Unit = println(toJson.toJson(value).toString)
printJson(User("Alex", 18))
CONTEXT BOUNDS
def printJson[T](value: T)(implicit toJson: ToJson[T]): Unit = println(toJson.toJson(value).toString)
\\ эквивалентны
def printJsonWithContextBound[T : ToJson](value: T): Unit = println(implicitly[ToJson[T]].toJson(value).toString)
\\ несколько ограничений одновременно
def func[T >: Cat <: Animal with CanMove : Decoder : Encoder] = ???
Инвариантность (Invariance)
case class Box[A](value: A)
def getName(box: Box[Animal]): String = box.value.name
val box = Box(Cat("Kuzya"))
getName(box)
// error: type mismatch;
// found : Box[Cat]
// required: Box[Animal]
Cat <: Animal, но Box инвариантен по типу T.
Инвариантность: A =:= B => F[A] =:= F[B]
Box[Animal] =:= Box[Animal]
Box[Cat] =:= Box[Cat]
Box[Cat] <: Box[Animal] - неверно!
Ковариантность (Covariance)
Ковариантность: A <: B => F[A] <: F[B]
Box[Cat] <: Box[Animal]
case class Box[+A](value: A)
def getName(box: Box[Animal]): String = box.value.name
val box = Box(Cat("Kuzya")) // ok
Почему не использовать ковариантность всегда?
Ковариантность (Covariance)
trait Box[+A] {
val value: A
def contains(a: A): Boolean
}
// Потенциальная опасность получить RunTimeError
trait Animal
case class Cat(name: String) extends Animal
case class Dog(age: Int) extends Animal
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains(a: Cat): Boolean = value.name == a.name
}
val animalBox: Box[Animal] = catBox
animalBox.contains(Dog(12)) // error
К счастью, компилятор не даст нам сделать эту ошибку. Во время компиляции будет ошибка:
Covariant type A occurs in contravariant position in type A of value a
Ковариантность (Covariance)
trait Box[+A] {
val value: A
def contains[B >: A](a: B): Boolean
}
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains(a: Cat): Boolean = value.name == a.name // compile error
}
val catBox = new Box[Cat] {
override val value: Cat = Cat("Sinus")
override def contains[B >: Cat](a: B): Boolean = ???
}
Контрвариантность (Contravariance)
Ковариантность: B <: A => F[A] <: F[B]
Printer[Animal] <: Printer[Cat]
trait Printer[-T] {
def print(value: T): String
}
val catPrinter = new Printer[Cat] {
override def print(cat: Cat): String =
cat.name + " is a cat!"
}
val animalPrinter = new Printer[Animal] {
override def print(animal: Animal): String =
animal match {
case Cat(name) => name + " is a cat!"
case Dog(name) => name + " is a dog!"
}
}
Printer[Cat] - может печатать только котов
Printer[Animal] - может печатать любых животных
Option
Множества (Set)
Отображения (Map)
Последовательности (Seq)
По умолчанию используются неизменяемые (иммутабельные) коллекции.
val list = List(1, 2, 3)
//list += 4 // value += is not a member of List[Int]
//list(0) = 4000 // value update is not a member of List[Int]
val mutList = collection.mutable.MutableList(1, 2, 3)
// MutableList(1000, 2, 3, 4)
mutList += 4
mutList(0) = 1000
val array = Array(1, 2, 3) // Array(1000, 2, 3)
//array += 4 // value += is not a member of Array[Int]
array(0) = 1000
Неизменяемые
Изменяемые
Array
val arr1 = new Array[Int](5)
//arr1: Array[Int] = Array(0, 0, 0, 0, 0)
val arr2 = Array(5, 4, 3, 2, 1)
//arr2: Array[Int] = Array(5, 4, 3, 2, 1)
val item = arr2(0)
//item: Int = 5
arr2.update(0, 9)
val item2 = arr2(0)
//item: Int = 9
List
Списки в scala однородны и ковариантны (List[+A]).
List
Создание списков
val list0 = List() // list0: List[Nothing] = List()
val list1 = List(1, 2, 3) // list1: List[Int] = List(1, 2, 3)
val list2 = List.range(1, 5) // list2: List[Int] = List(1, 2, 3, 4)
val list2a = List.range(1, 5, 2) // list2a: List[Int] = List(1, 3)
val list3 = List.fill(5)("t") // list3: List[String] = List("t", "t", "t", "t", "t")
val list4 = List.tabulate(5)(n => n / 2.0) // list4: List[Double] = List(0.0, 0.5, 1.0, 1.5, 2.0)
//Nil - пустой список
val list5 = Nil // d: Nil.type = List()
val list6 = 3 :: 2 :: 1 :: Nil // list: List[Int] = List(3, 2, 1)
val list6a = 4 :: list6 // List(4, 3, 2, 1)
List
Как извлечь элементы List
val list = List(1, 2, 3, 4, 5)
// индексация списка с помощью круглых скобок (O(n))
val e1 = List(3) // 4
// шаблоны
val List(a, b, c) = List(1, 2, 3)
val x :: y :: z = List(1, 2, 3) // z == List(3)
val m :: n :: k = list // k == List(3, 4, 5)
List
Как извлечь элементы List
// pattern-matching
List(1,2,3) match {
case x => "one"
case x :: y => "two"
case x :: y :: z => "three"
} // res0: String = one
List(1,2,3,4,5) match {
case x :: y :: z => "three"
case x :: y => "two"
case x => "one"
} // res1: String = "three"
List(1,2,3) match {
case x :: Nil => "one"
case x :: y :: Nil => "two"
case x :: y :: z :: Nil => "three"
}
List
Как добавить элементы
3 :: List(1,2)
// res3: List[Int] = List(3, 1, 2)
//List(1, 2) :: 3 // value :: is not a member of Int
List(1, 2) :+ 3 // вставка в конец List - плохая идея
// res4: List[Int] = List(1, 2, 3)
3 +: List(1, 2) // +: - может использовать для любых коллекций
// res5: List[Int] = List(3, 1, 2)
// Операция только для листов
val `2list1` = List(1, 2) ::: List(3, 4, 5)
//`2list1`: List[Int] = List(1, 2, 3, 4, 5)
val `2list2`= List() ::: List(1, 2, 3)
//`2list2`: List[Int] = List(1, 2, 3)
// Операция для любых коллекций
List(1, 2, 3) ++ List(4, 5)
// res5: List[Int] = List(1, 2, 3, 4, 5)
List(1,2) ++ Some(3)
// res8: List[Int] = List(1, 2, 3)
length, indices
val len1 = List(1, 2, 3).length
//len1: Int = 3
val len2 = List().length
//len2: Int = 0
val len3 = Nil.length
//len3: Int = 0
val idx1 = List(1, 2, 3).indices
//idx1: Range = Range(0, 1, 2)
val idx2 = Nil.indices
//idx2: Range = Range()
head, tail, isEmpty
val head = List(1, 2, 3).head
//head: Int = 1
List(1, 2, 3).headOption
// res5: Option[Int] = Some(1)
val tail = List(1, 2, 3, 4, 5).tail
//tail: List[Int] = List(2, 3, 4, 5)
val isEmpty = List(1, 2, 3, 4, 5).isEmpty
//isEmpty: Boolean = false
val isEmptyNil = Nil.isEmpty
//isEmptyNil: Boolean = true
last, init, reverse
val abcde = List('a', 'b', 'c', 'd', 'e')
//abcde: List[Char] = List('a', 'b', 'c', 'd', 'e')
val last1 = abcde.last
//last1: Char = 'e'
val init1 = abcde.init
//init1: List[Char] = List('a', 'b', 'c', 'd')
List().init
//java.lang.UnsupportedOperationException: init of empty list
// scala.collection.immutable.Nil$.init(List.scala:596)
// ...
List().last
//java.util.NoSuchElementException: last of empty list
// scala.collection.immutable.Nil$.last(List.scala:595)
// ...
val edcba = List("e", "d", "c", "b", "a")
//edcba: List[String] = List("e", "d", "c", "b", "a")
val reverse1 = edcba.reverse
//reverse1: List[String] = List("a", "b", "c", "d", "e")
contains, distinct
List(1, 2, 3).contains(3)
// Boolean = true
List(1, 2, 3).contains(0)
// Boolean = false
List(1, 2, 2, 3, 3, 3).distinct
// res2: List[Int] = List(1, 2, 3)
case class Cat(name: String)
List(Cat("Sam"), Cat("Nyan"), Cat("Nyan")).distinct
// List(Cat("Sam"), Cat("Nyan"))
class SimpleCat(name: String)
List(new SimpleCat("Nyan") :: new SimpleCat("Nyan")).distinct.length
// 2
drop, take, splitAt
val abcde = List('a', 'b', 'c', 'd', 'e')
val teke1 = abcde.take(2)
//teke1: List[Char] = List('a', 'b')
val drop1 = abcde.drop(2)
//drop1: List[Char] = List('c', 'd', 'e')
val splitat1 = abcde.splitAt(2)
//(List('a', 'b'), List('c', 'd', 'e'))
flatten
List(List(1, 2), List(3), List(), List(4,5)).flatten
//List[Int] = List(1, 2, 3, 4, 5)
val flatten2 = List(1, 2, 3).flatten
//No implicit view available from Int => IterableOnce[B]
//val flatten2 = List(1, 2, 3).flatten
List(List(List(), List(1)), List(List(2))).flatten
//List[List[Int]] = List(List(), List(1), List(2))
List(Some(1), Some(2), None).flatten
// : List[Int] = List(1, 2)
zip, unzip
val abcde = List('a', 'b', 'c', 'd', 'e')
val zip1 = abcde.indices.zip(abcde)
// Vector((0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'))
val zip2 = abcde.zip(List(1, 2, 3))
// List(('a', 1), ('b', 2), ('c', 3))
val zipIdx = abcde.zipWithIndex
// List(('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4))
val unzip = zip2.unzip
// (List('a', 'b', 'c'), List(1, 2, 3))
toString, mkString
val abcde = List('a', 'b', 'c', 'd', 'e')
val abcStr = abcde.toString
//abcStr: String = "List(a, b, c, d, e)"
val list = List(1, 2, 3, 4, 5)
val lstStr = list.toString
//lstStr: String = "List(1, 2, 3, 4, 5)"
val str1 = abcde.mkString("[", ",", "]")
//str1: String = "[a,b,c,d,e]"
val str3 = abcde.mkString(",")
//str3: String = "a,b,c,d,e"
map, foreach
List(1,2,3).map(_ * 2).map(_.toString())
// res0: List[String] = List("2", "4", "6")
List(1,2,3).foreach(_.toString()) // ()
List(1,2,3).foreach(println)
// 1
// 2
// 3
Ключевое отличие в том, что map возвращает значение, а foreache делает какой-то сайд-эффект.
flatMap
val listOpt = List(1,2,3).map(Some(_))
// listOpt: List[Some[Int]] = List(Some(1), Some(2), Some(3))
listOpt.flatten
// res3: List[Int] = List(1, 2, 3)
List(1,2,3).flatMap(Some(_))
// res4: List[Int] = List(1, 2, 3)
flatMap - это последовательно применённый map, flatten
map, flatmap
val li = 3 :: 2 :: 1 :: Nil
val filterOpt: Int => Option[Int] = x => if (x % 2 == 0) Some(x) else None
for {
item <- li
itemPlus = item + 1
itemAfterOpt <- filterOpt(itemPlus)
} yield itemAfterOpt
// List(4, 2)
li
.map { item => val itemPlus = item + 1; (item, itemPlus) }
.flatMap { case (item, itemPlus) =>
filterOpt(itemPlus)
.map(itemAfterOpt => itemAfterOpt)
}
filter, partition, find, groupBy
val filter1 = List(1, 2, 3, 4, 5).filter(_ % 2 == 0)
//filter1: List[Int] = List(2, 4)
val filter2 = List(1, 2, 3, 4, 5).filter(_ < 0)
/filter2: List[Int] = List()
val partition1 = List(1, 2, 3, 4, 5).partition(_ % 2 == 0)
//(List[Int], List[Int]) = (List(2, 4), List(1, 3, 5))
val partition2 = List(1, 2, 3, 4, 5).partition(_ < 0)
//(List[Int], List[Int]) = (List(), List(1, 2, 3, 4, 5))
val find1 = List(1, 2, 3, 4, 5).find(_ % 2 == 0)
//find1: Option[Int] = Some(2)
val find2 = List(1, 2, 3, 4, 5).find(_ <= 0)
//find2: Option[Int] = None
List(Cat("Sam"), Cat("Nyan"), Cat("Nyan")).groupBy(_.name)
//HashMap(
// "Sam" -> List(Cat("Sam")),
// "Nyan" -> List(Cat("Nyan"), Cat("Nyan")
//)
distinctBy
List(Cat("Sam"), Cat("Nyan"), Cat("Nyan")).groupBy(_.name)
//HashMap(
// "Sam" -> List(Cat("Sam")),
// "Nyan" -> List(Cat("Nyan"), Cat("Nyan")
//)
List(Cat("Sam"), Cat("Nyan"), Cat("Nyan"))
.groupBy(_.name)
.flatMap(_._2.headOption)
// List(Cat(name = "Sam"), Cat(name = "Nyan"))
List(Cat("Sam"), Cat("Nyan"), Cat("Nyan")).distinctBy(_.name)
// List(Cat(name = "Sam"), Cat(name = "Nyan"))
takeWhile, dropWhile, span, forAll, exist
val takeWhile1 = List(1, 2, 3, -4, 5).takeWhile(_ > 0)
//takeWhile1: List[Int] = List(1, 2, 3)
val dropWhile1 = List("banana", "pear", "apple", "orange")
.dropWhile(_.startsWith("b"))
//dropWhile2: List[String] = List("pear", "apple", "orange")
val lst = List(1, 2, 3, -4, 5)
val span1 = lst.span(_ > 0)
//span1: (List[Int], List[Int]) = (List(1, 2, 3), List(-4, 5))
val span2 = (lst.takeWhile(_ > 0), lst.dropWhile(_ > 0) )
//span2: (List[Int], List[Int]) = (List(1, 2, 3), List(-4, 5))
val forall1 = List(1, 2, 3).forall( _ > 0)
//forall1: Boolean = true
val forall2 = List(1, 2,-3).forall( _ > 0)
//forall2: Boolean = false
val exists1 = List(1, 2,-3).exists( _ < 0)
//exists1: Boolean = true
val exists2 = List(1, 2, 3).exists( _ < 0)
//exists2: Boolean = false
foldLeft, foldRight, fold
val num = List(1, 2, 3)
val foldLeft1 = num.foldLeft(0)(_ + _)
//foldLeft1: Int = 6
val foldLeft2 = num.foldLeft(-6)(_ + _)
//foldLeft2: Int = 0
val foldRight1 = num.foldRight(0)(_ + _)
//foldRight1: Int = 6
val foldRight2 = num.foldRight(-6)(_ + _)
//foldRight2: Int = 0
List(1,2,3).foldLeft("0")((res, num) => res + num.toString)
// 0123
List(1,2,3).foldRight("0")((num, res) => res + num.toString)
// 0321
List(1,2,3).fold("")(_ + _.toString())
// 123
foldLeft, foldRight
reduceLeft, reduceRight, reduce
val num = List(1, 2, 3)
val reduceLeft1 = lst.reduceLeft(_ + _)
//reduceLeft1: Int = 6
val reduceLeft2 = num.reduceLeft(_ min _)
//reduceLeft2: Int = 1
val reduceRight1 = num.reduceRight(_ + _)
//reduceRight1: Int = 6
val reduceRight2 = num.reduceRight(_ max _)
//reduceRight2: Int = 3
fold VS reduce
val emptyLst = List.empty[String]
val foldLeftEmpty = emptyLst.foldLeft("i")(_ + _)
//foldLeftEmpty: String = "i"
List.empty[Int].reduceLeft(_ + _)
//UnsupportedOperationException: empty.reduceLeft
List.empty[Int].reduceLeftOption(_ + _)
// : Option[Int] = None
sorted, sortWith
val a = List(10, 5, 8, 1, 7).sorted
//sort4: List[Int] = List(1, 5, 7, 8, 10)
case class Cat (name: String, age: Int)
val s = List(Cat("Мурзик", 2), Cat("Murka", 1)).sorted
// error: No implicit Ordering defined for Cat.
List(4, 1, 5, 2, 3).sortWith(_ > _)
// res9: List[Int] = List(5, 4, 3, 2, 1)
case class Cat(name: String, age: Int)
val s = List(Cat("Мурзик", 2), Cat("Murka", 1))
// s: List[Cat] = List(Cat("Мурзик", 2), Cat("Murka", 1))
s.sortWith(_.name < _.name)
// res10: List[Cat] = List(Cat("Murka", 1), Cat("Мурзик", 2))
Представление - это базовая коллекция, для которой все трансформеры выполняются ленивым образом.
List(1, 2, 3)
.map(_ * 2) // List(2, 4, 6)
.map(_ + 3) // List(5, 7, 9)
List(1, 2, 3)
.view
.map(_ * 2)
.map(_ + 3)
.toList // List(5, 7, 9)
Map
// Как создать отображение
val map1 = Map()
val map2 = Map(("Alex", 32), ("Sid", 22), ("Max", 44))
val map3 = Map(
"Alex" -> 32,
"Sid" -> 22,
"Max" -> 44,
)
val age: Int = map3("Max")
// возможен NoSuchElementException
val age2: Option[Int] = map3.get("Dan")
val age3: Int = map3.getOrElse("Dan", 0)
Map
keys, values, map
val ageMap = Map("Alex" -> 32, "Sid" -> 22, "Max" -> 44)
val keys: Iterable[String] = ageMap.keys
val values: Iterable[Int] = ageMap.values
val mapWithMap1 = ageMap.map { keyAndValue =>
keyAndValue match {
case (key, value) => key + value.toString
}
}
val mapWithMap2 = ageMap.map {
case (key, value) => key + value
}
Option
Располагается в пакете scala, а не вместе с коллекциями: scala.Option
Может содержать ноль (None) или один (Some) элемент.
// Как Option выглядит внутри
sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable { ... }
final case class Some[+A](value: A) extends Option[A] { def get: A = value }
case object None extends Option[Nothing] { def get: Nothing = throw new NoSuchElementException("None.get") }
// Как использовать
val option1: Option[Int] = Option(1)
val option2: Option[Int] = Option.empty[Int]
val some: Option[Int] = Some(1)
val none: Option[Int] = None
val pipeline = some
.map(_ + 1)
.filter(_ % 2 == 1)
.orElse(None)
.getOrElse(-1)
Try
Try позволяет обрабатывать возможные исключения
import scala.util.{Failure, Success, Try}
val result = Try(12 / 0) match {
case Failure(exception: ArithmeticException) => -1
case Failure(exception) => -1
case Success(value) => value
}
// На Try можно делать map и flatMap
val try1 = Try(12).map(_ + 1).flatMap(v => Try(v / 0))
// преобразование к другим типам
val toOption: Option[Int] = try1.toOption
val toEither: Either[Throwable, Int] = try1.toEither
Either
Either содержит либо значение (Right), либо ошибку (Left)
val either: Either[Throwable, Int] = Either.cond(10 > 22, right = 1, left = new ArithmeticException)
val right: Right[Nothing, String] = Right("some value")
val left: Left[String, Nothing] = Left("some error")
val pipeline: Either[String, String] = right.map(_ + " 2").flatMap(_ => left)
val toOption = either.toOption