pajatopmr
03/27/2021, 6:21 AMCLOVIS
03/27/2021, 8:24 AMfoldRight
correctly: your length function should be:
fun <A> length(xs: List<A>) = foldRight(xs) { e, acc -> when(e) {
is Nil -> 1
is Cons -> 1 + acc
}}
The point of using fold
is to not have to handle recursion yourself.CLOVIS
03/27/2021, 8:31 AM.tail
implemented?raulraja
03/27/2021, 9:12 AMlength
can be just defined as:
fun <A> List<A>.length(): Int =
foldRight(0) { _, acc -> 1 + acc }
May have to change the arg order if in your case you are declaring these as top level functions without a receiver:
fun <A> length(xs: List<A>): Int =
foldRight(xs, 0) { _, acc -> 1 + acc }
Since fold will reduce all elements of the list into a final summary value List<A> -> B
let’s call it B
the length of the list can be determined by making B
be an Int
and adding the start counter at 0.
In all folds first you provide or have to deal with the empty identity but pattern matching and folding it’s the same. If you already have foldRight
defined, then most other operations can be expressed in terms of it.
When we code with exhaustive pattern matching or when we fold we are effectively evaluating all possible cases in which an algebraic data type (sealed hierarchy) can be on. For example List
can be Cons
or Nil
in your case. When you fold, the operation asks you to provide the empty identity. We provide 0 as that empty identity for the Int monoid which is what we want to return if the list is Nil
. Then we provide a function that adds one to the acc
to count how many elements there are. This function is is the associative operation needed for the list structure to be turned into a single final value.
I think this exercise tries to teach you:
1. Data structures can be represented algebraically with recursive sealed data types (side note, not efficient)
2. Pattern matching in all cases is the same as fold, fold is in fact implemented as such.
3. All terminal operations of any data structures whether it’s a List, IO or whatever are just specialisations of folds including length.
4. All of these terminal operations involve whether implicit or explicitly the notion of an empty identity, in the case of length 0
which is what it’s returned if the structure is emptyraulraja
03/27/2021, 9:17 AMpajatopmr
03/27/2021, 2:22 PMsealed class List<out A> {
companion object {
fun <A> of(vararg aa: A): List<A> {
val tail = aa.sliceArray(1 until aa.size)
return if (aa.isEmpty()) Nil else Cons(aa[0], of(*tail))
}
fun <A> empty(): List<A> = Nil
}
}
object Nil : List<Nothing>() {
override fun toString(): String = "Nil"
}
data class Cons<out A>(
val head: A,
val tail: List<A>
) : List<A>()
Monoids is coming up soon.pajatopmr
03/27/2021, 2:28 PMpajatopmr
03/27/2021, 2:37 PMjulian
03/27/2021, 6:52 PMpajatopmr
03/28/2021, 5:08 AM