Давайте начнем с нуля, это должно помочь вам понять концепции.
Мы создадим наш собственный простой функционал Список .
Он будет называться MyList
, будет иметьпустой список с именем MyNil
и класс / оператор cons как :!:
.
// Here we create the type MyList.
// The sealed is used to signal that the only valid implementations
// will be part of this file. This is because, a List is an ADT.
// The A (which could be a T or whatever) is just a type parameter
// it means that our list can work with any arbitrary type (like Int, String or My Class)
// and we just give it a name, in order to be able to refer to it in the code.
// Finally, the plus (+) sign, tells the compiler that MyList is covariant in A.
// That means: If A <: B Then MyList[A] <: MyList[B]
// (<: means subtype of)
sealed trait MyList[+A] {
def head: A // Here we say that the head of a List of As is an A.
def tail: MyList[A] // As well, a tail of a List of As is another list of As.
// Here we define the cons operator, to prepend elements to the list.
// You can see that it will just create a new cons class with the new element as the head & this as the tail.
// Now, you may be wondering why we added a new type B and why it must be a super type of A
// You can check out this answer of mine:
// https://stackoverflow.com/questions/54163830/implementing-a-method-inside-a-scala-parameterized-class-with-a-covariant-type/54164135#54164135
final def :!:[B >: A](elem: B): MyList[B] =
new :!:(elem, this)
// Finally, foldRigh!
// You can see that we added a new type parameter B.
// In this case, it does not have any restriction because the way fold works.
final def foldRight[B](z: B)(op: (A, B) => B): B = this match {
case MyNil => z
case h :!: t => op(h, t.foldRight(z)(op))
}
}
object MyList {
// Factory.
def apply[A](elems: A*): MyList[A] =
if (elems.nonEmpty) {
elems.head :!: MyList(elems.tail : _*)
} else {
MyNil
}
}
// Implementations of the MyList trait.
final case class :!:[+A](head: A, tail: MyList[A]) extends MyList[A]
final case object MyNil extends MyList[Nothing] {
override def head = throw new NoSuchElementException("head of empty list")
override def tail = throw new NoSuchElementException("tail of empty list")
}
Теперь вы можете:
val l1 = MyList(2, 3, 4) // l1: MyList[Int] = 2 :!: 3 :!: 4 :!: MyNil
val l2 = 1 :!: l1 // // l2: MyList[Int] = 1 :!: 2 :!: 3 :!: 4 :!: MyNil
val sum = l2.foldRight(0)(_ + _) // sum: Int = 10