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