Lets start by scratch, that should help you grasp the concepts.
We will create our own simple functional List.
It will be called MyList
, will have an empty list called MyNil
and the cons class / operator as :!:
.
// 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://stackguides.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")
}
Now you can:
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
List[T]
? – Luis Miguel Mejía SuárezT
somewhere. Anyways, it is kind of weird to have the method receive another List. Especially because that is the list from the stdlib... what exactly do you want to do? Define your own List class with a foldRight method? Or your own foldRight function? – Luis Miguel Mejía SuárezNil
and::
come from the stdlib. – ThiloMyList[T]
,MyNil
,myConcat
. You don't really care about whatT
is (assuming your list can work with any type of element). – Thilo