Skip to content

Commit

Permalink
Merge pull request fpinscala#400 from aloshbennett/master
Browse files Browse the repository at this point in the history
Split lengthy comments into multiple lines.
  • Loading branch information
runarorama committed Mar 19, 2015
2 parents d375345 + 4f1ce85 commit 0ec6fee
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 25 deletions.
90 changes: 68 additions & 22 deletions answers/src/main/scala/fpinscala/datastructures/List.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package fpinscala.datastructures

sealed trait List[+A] // `List` data type, parameterized on a type, `A`
case object Nil extends List[Nothing] // A `List` data constructor representing the empty list
case class Cons[+A](head: A, tail: List[A]) extends List[A] // Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`, which may be `Nil` or another `Cons`.
/* Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`,
which may be `Nil` or another `Cons`.
*/
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List { // `List` companion object. Contains functions for creating and working with lists.
def sum(ints: List[Int]): Int = ints match { // A function that uses pattern matching to add up a list of integers
Expand Down Expand Up @@ -52,9 +55,12 @@ object List { // `List` companion object. Contains functions for creating and wo
*/

/*
Although we could return `Nil` when the input list is empty, we choose to throw an exception instead. This is a somewhat subjective choice. In our experience, taking the tail of an empty list is often a bug, and silently returning a value just means this bug will be discovered later, further from the place where it was introduced.
Although we could return `Nil` when the input list is empty, we choose to throw an exception instead. This is
a somewhat subjective choice. In our experience, taking the tail of an empty list is often a bug, and silently
returning a value just means this bug will be discovered later, further from the place where it was introduced.
It's generally good practice when pattern matching to use `_` for any variables you don't intend to use on the right hand side of a pattern. This makes it clear the value isn't relevant.
It's generally good practice when pattern matching to use `_` for any variables you don't intend to use on the
right hand side of a pattern. This makes it clear the value isn't relevant.
*/
def tail[A](l: List[A]): List[A] =
l match {
Expand All @@ -63,15 +69,20 @@ object List { // `List` companion object. Contains functions for creating and wo
}

/*
If a function body consists solely of a match expression, we'll often put the match on the same line as the function signature, rather than introducing another level of nesting.
If a function body consists solely of a match expression, we'll often put the match on the same line as the
function signature, rather than introducing another level of nesting.
*/
def setHead[A](l: List[A], h: A): List[A] = l match {
case Nil => sys.error("setHead on empty list")
case Cons(_,t) => Cons(h,t)
}

/*
Again, it's somewhat subjective whether to throw an exception when asked to drop more elements than the list contains. The usual default for `drop` is not to throw an exception, since it's typically used in cases where this is not indicative of a programming error. If you pay attention to how you use `drop`, it's often in cases where the length of the input list is unknown, and the number of elements to be dropped is being computed from something else. If `drop` threw an exception, we'd have to first compute or check the length and only drop up to that many elements.
Again, it's somewhat subjective whether to throw an exception when asked to drop more elements than the list
contains. The usual default for `drop` is not to throw an exception, since it's typically used in cases where this
is not indicative of a programming error. If you pay attention to how you use `drop`, it's often in cases where the
length of the input list is unknown, and the number of elements to be dropped is being computed from something else.
If `drop` threw an exception, we'd have to first compute or check the length and only drop up to that many elements.
*/
def drop[A](l: List[A], n: Int): List[A] =
if (n <= 0) l
Expand All @@ -81,7 +92,9 @@ object List { // `List` companion object. Contains functions for creating and wo
}

/*
Somewhat overkill, but to illustrate the feature we're using a _pattern guard_, to only match a `Cons` whose head satisfies our predicate, `f`. The syntax is to add `if <cond>` after the pattern, before the `=>`, where `<cond>` can use any of the variables introduced by the pattern.
Somewhat overkill, but to illustrate the feature we're using a _pattern guard_, to only match a `Cons` whose head
satisfies our predicate, `f`. The syntax is to add `if <cond>` after the pattern, before the `=>`, where `<cond>` can
use any of the variables introduced by the pattern.
*/
def dropWhile[A](l: List[A], f: A => Boolean): List[A] =
l match {
Expand All @@ -90,9 +103,14 @@ object List { // `List` companion object. Contains functions for creating and wo
}

/*
Note that we're copying the entire list up until the last element. Besides being inefficient, the natural recursive solution will use a stack frame for each element of the list, which can lead to stack overflows for large lists (can you see why?). With lists, it's common to use a temporary, mutable buffer internal to the function (with lazy lists or streams, which we discuss in chapter 5, we don't normally do this). So long as the buffer is allocated internal to the function, the mutation is not observable and RT is preserved.
Another common convention is to accumulate the output list in reverse order, then reverse it at the end, which doesn't require even local mutation. We'll write a reverse function later in this chapter.
Note that we're copying the entire list up until the last element. Besides being inefficient, the natural recursive
solution will use a stack frame for each element of the list, which can lead to stack overflows for
large lists (can you see why?). With lists, it's common to use a temporary, mutable buffer internal to the
function (with lazy lists or streams, which we discuss in chapter 5, we don't normally do this). So long as the
buffer is allocated internal to the function, the mutation is not observable and RT is preserved.
Another common convention is to accumulate the output list in reverse order, then reverse it at the end, which
doesn't require even local mutation. We'll write a reverse function later in this chapter.
*/
def init[A](l: List[A]): List[A] =
l match {
Expand All @@ -113,11 +131,15 @@ object List { // `List` companion object. Contains functions for creating and wo
}

/*
No, this is not possible! The reason is because _before_ we ever call our function, `f`, we evaluate its argument, which in the case of `foldRight` means traversing the list all the way to the end. We need _non-strict_ evaluation to support early termination---we discuss this in chapter 5.
No, this is not possible! The reason is because _before_ we ever call our function, `f`, we evaluate its argument,
which in the case of `foldRight` means traversing the list all the way to the end. We need _non-strict_ evaluation
to support early termination---we discuss this in chapter 5.
*/

/*
We get back the original list! Why is that? As we mentioned earlier, one way of thinking about what `foldRight` "does" is it replaces the `Nil` constructor of the list with the `z` argument, and it replaces the `Cons` constructor with the given function, `f`. If we just supply `Nil` for `z` and `Cons` for `f`, then we get back the input list.
We get back the original list! Why is that? As we mentioned earlier, one way of thinking about what `foldRight` "does"
is it replaces the `Nil` constructor of the list with the `z` argument, and it replaces the `Cons` constructor with
the given function, `f`. If we just supply `Nil` for `z` and `Cons` for `f`, then we get back the input list.
foldRight(Cons(1, Cons(2, Cons(3, Nil))), Nil:List[Int])(Cons(_,_))
Cons(1, foldRight(Cons(2, Cons(3, Nil)), Nil:List[Int])(Cons(_,_)))
Expand All @@ -130,7 +152,9 @@ object List { // `List` companion object. Contains functions for creating and wo
foldRight(l, 0)((_,acc) => acc + 1)

/*
It's common practice to annotate functions you expect to be tail-recursive with the `tailrec` annotation. If the function is not tail-recursive, it will yield a compile error, rather than silently compiling the code and resulting in greater stack space usage at runtime.
It's common practice to annotate functions you expect to be tail-recursive with the `tailrec` annotation. If the
function is not tail-recursive, it will yield a compile error, rather than silently compiling the code and resulting
in greater stack space usage at runtime.
*/
@annotation.tailrec
def foldLeft[A,B](l: List[A], z: B)(f: (B, A) => B): B = l match {
Expand All @@ -146,9 +170,15 @@ object List { // `List` companion object. Contains functions for creating and wo
def reverse[A](l: List[A]): List[A] = foldLeft(l, List[A]())((acc,h) => Cons(h,acc))

/*
The implementation of `foldRight` in terms of `reverse` and `foldLeft` is a common trick for avoiding stack overflows when implementing a strict `foldRight` function as we've done in this chapter. (We'll revisit this in a later chapter, when we discuss laziness).
The other implementations build up a chain of functions which, when called, results in the operations being performed with the correct associativity. We are calling `foldRight` with the `B` type being instantiated to `B => B`, then calling the built up function with the `z` argument. Try expanding the definitions by substituting equals for equals using a simple example, like `foldLeft(List(1,2,3), 0)(_ + _)` if this isn't clear. Note these implementations are more of theoretical interest - they aren't stack-safe and won't work for large lists.
The implementation of `foldRight` in terms of `reverse` and `foldLeft` is a common trick for avoiding stack overflows
when implementing a strict `foldRight` function as we've done in this chapter. (We'll revisit this in a later chapter,
when we discuss laziness).
The other implementations build up a chain of functions which, when called, results in the operations being performed
with the correct associativity. We are calling `foldRight` with the `B` type being instantiated to `B => B`, then
calling the built up function with the `z` argument. Try expanding the definitions by substituting equals for equals
using a simple example, like `foldLeft(List(1,2,3), 0)(_ + _)` if this isn't clear. Note these implementations are
more of theoretical interest - they aren't stack-safe and won't work for large lists.
*/
def foldRightViaFoldLeft[A,B](l: List[A], z: B)(f: (A,B) => B): B =
foldLeft(reverse(l), z)((b,a) => f(a,b))
Expand All @@ -160,15 +190,23 @@ object List { // `List` companion object. Contains functions for creating and wo
foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)

/*
`append` simply replaces the `Nil` constructor of the first list with the second list, which is exactly the operation performed by `foldRight`.
`append` simply replaces the `Nil` constructor of the first list with the second list, which is exactly the operation
performed by `foldRight`.
*/
def appendViaFoldRight[A](l: List[A], r: List[A]): List[A] =
foldRight(l, r)(Cons(_,_))

/*
Since `append` takes time proportional to its first argument, and this first argument never grows because of the right-associativity of `foldRight`, this function is linear in the total length of all lists. You may want to try tracing the execution of the implementation on paper to convince yourself that this works.
Note that we're simply referencing the `append` function, without writing something like `(x,y) => append(x,y)` or `append(_,_)`. In Scala there is a rather arbitrary distinction between functions defined as _methods_, which are introduced with the `def` keyword, and function values, which are the first-class objects we can pass to other functions, put in collections, and so on. This is a case where Scala lets us pretend the distinction doesn't exist. In other cases, you'll be forced to write `append _` (to convert a `def` to a function value) or even `(x: List[A], y: List[A]) => append(x,y)` if the function is polymorphic and the type arguments aren't known.
Since `append` takes time proportional to its first argument, and this first argument never grows because of the
right-associativity of `foldRight`, this function is linear in the total length of all lists. You may want to try
tracing the execution of the implementation on paper to convince yourself that this works.
Note that we're simply referencing the `append` function, without writing something like `(x,y) => append(x,y)`
or `append(_,_)`. In Scala there is a rather arbitrary distinction between functions defined as _methods_, which are
introduced with the `def` keyword, and function values, which are the first-class objects we can pass to other
functions, put in collections, and so on. This is a case where Scala lets us pretend the distinction doesn't exist.
In other cases, you'll be forced to write `append _` (to convert a `def` to a function value)
or even `(x: List[A], y: List[A]) => append(x,y)` if the function is polymorphic and the type arguments aren't known.
*/
def concat[A](l: List[List[A]]): List[A] =
foldRight(l, Nil:List[A])(append)
Expand All @@ -180,7 +218,10 @@ object List { // `List` companion object. Contains functions for creating and wo
foldRight(l, Nil:List[String])((h,t) => Cons(h.toString,t))

/*
A natural solution is using `foldRight`, but our implementation of `foldRight` is not stack-safe. We can use `foldRightViaFoldLeft` to avoid the stack overflow (variation 1), but more commonly, with our current implementation of `List`, `map` will just be implemented using local mutation (variation 2). Again, note that the mutation isn't observable outside the function, since we're only mutating a buffer that we've allocated.
A natural solution is using `foldRight`, but our implementation of `foldRight` is not stack-safe. We can
use `foldRightViaFoldLeft` to avoid the stack overflow (variation 1), but more commonly, with our current
implementation of `List`, `map` will just be implemented using local mutation (variation 2). Again, note that the
mutation isn't observable outside the function, since we're only mutating a buffer that we've allocated.
*/
def map[A,B](l: List[A])(f: A => B): List[B] =
foldRight(l, Nil:List[B])((h,t) => Cons(f(h),t))
Expand Down Expand Up @@ -227,7 +268,11 @@ object List { // `List` companion object. Contains functions for creating and wo
flatMap(l)(a => if (f(a)) List(a) else Nil)

/*
To match on multiple values, we can put the values into a pair and match on the pair, as shown next, and the same syntax extends to matching on N values (see sidebar "Pairs and tuples in Scala" for more about pair and tuple objects). You can also (somewhat less conveniently, but a bit more efficiently) nest pattern matches: on the right hand side of the `=>`, simply begin another `match` expression. The inner `match` will have access to all the variables introduced in the outer `match`.
To match on multiple values, we can put the values into a pair and match on the pair, as shown next, and the same
syntax extends to matching on N values (see sidebar "Pairs and tuples in Scala" for more about pair and tuple
objects). You can also (somewhat less conveniently, but a bit more efficiently) nest pattern matches: on the
right hand side of the `=>`, simply begin another `match` expression. The inner `match` will have access to all the
variables introduced in the outer `match`.
The discussion about stack usage from the explanation of `map` also applies here.
*/
Expand All @@ -238,7 +283,8 @@ object List { // `List` companion object. Contains functions for creating and wo
}

/*
This function is usually called `zipWith`. The discussion about stack usage from the explanation of `map` also applies here. By putting the `f` in the second argument list, Scala can infer its type from the previous argument list.
This function is usually called `zipWith`. The discussion about stack usage from the explanation of `map` also
applies here. By putting the `f` in the second argument list, Scala can infer its type from the previous argument list.
*/
def zipWith[A,B,C](a: List[A], b: List[B])(f: (A,B) => C): List[C] = (a,b) match {
case (Nil, _) => Nil
Expand Down
11 changes: 9 additions & 2 deletions answers/src/main/scala/fpinscala/datastructures/Tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ object Tree {
}

/*
Like `foldRight` for lists, `fold` receives a "handler" for each of the data constructors of the type, and recursively accumulates some value using these handlers. As with `foldRight`, `fold(t)(Leaf(_))(Branch(_,_)) == t`, and we can use this function to implement just about any recursive function that would otherwise be defined by pattern matching.
Like `foldRight` for lists, `fold` receives a "handler" for each of the data constructors of the type, and recursively
accumulates some value using these handlers. As with `foldRight`, `fold(t)(Leaf(_))(Branch(_,_)) == t`, and we can use
this function to implement just about any recursive function that would otherwise be defined by pattern matching.
*/
def fold[A,B](t: Tree[A])(f: A => B)(g: (B,B) => B): B = t match {
case Leaf(a) => f(a)
Expand All @@ -63,7 +65,12 @@ object Tree {
fold(t)(a => Leaf(f(a)))(Branch(_,_))
^
This error is an unfortunate consequence of Scala using subtyping to encode algebraic data types. Without the annotation, the result type of the fold gets inferred as `Leaf[B]` and it is then expected that the second argument to `fold` will return `Leaf[B]`, which it doesn't (it returns `Branch[B]`). Really, we'd prefer Scala to infer `Tree[B]` as the result type in both cases. When working with algebraic data types in Scala, it's somewhat common to define helper functions that simply call the corresponding data constructors but give the less specific result type:
This error is an unfortunate consequence of Scala using subtyping to encode algebraic data types. Without the
annotation, the result type of the fold gets inferred as `Leaf[B]` and it is then expected that the second argument
to `fold` will return `Leaf[B]`, which it doesn't (it returns `Branch[B]`). Really, we'd prefer Scala to
infer `Tree[B]` as the result type in both cases. When working with algebraic data types in Scala, it's somewhat
common to define helper functions that simply call the corresponding data constructors but give the less specific
result type:
def leaf[A](a: A): Tree[A] = Leaf(a)
def branch[A](l: Tree[A], r: Tree[A]): Tree[A] = Branch(l, r)
Expand Down
5 changes: 4 additions & 1 deletion exercises/src/main/scala/fpinscala/datastructures/List.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package fpinscala.datastructures

sealed trait List[+A] // `List` data type, parameterized on a type, `A`
case object Nil extends List[Nothing] // A `List` data constructor representing the empty list
case class Cons[+A](head: A, tail: List[A]) extends List[A] // Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`, which may be `Nil` or another `Cons`.
/* Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`,
which may be `Nil` or another `Cons`.
*/
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List { // `List` companion object. Contains functions for creating and working with lists.
def sum(ints: List[Int]): Int = ints match { // A function that uses pattern matching to add up a list of integers
Expand Down

0 comments on commit 0ec6fee

Please sign in to comment.