Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: scala
scala:
- 2.12.10
- 2.13.1
jdk:
- openjdk8
script:
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ProjectPlugin.autoImport._

val scalaExercisesV = "0.5.0-SNAPSHOT"
val scalaExercisesV = "0.6.0-SNAPSHOT"

def dep(artifactId: String) = "org.scala-exercises" %% artifactId % scalaExercisesV

Expand Down
4 changes: 2 additions & 2 deletions project/ProjectPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object ProjectPlugin extends AutoPlugin {
object autoImport {

lazy val V = new {
val scala212: String = "2.12.10"
val scala213: String = "2.13.1"
val shapeless: String = "2.3.3"
val scalatest: String = "3.1.0"
val scalatestplusScheck: String = "3.1.0.0-RC2"
Expand All @@ -39,7 +39,7 @@ object ProjectPlugin extends AutoPlugin {
organizationEmail = "[email protected]"
),
orgLicenseSetting := ApacheLicense,
scalaVersion := V.scala212,
scalaVersion := V.scala213,
scalaOrganization := "org.scala-lang",
resolvers ++= Seq(
Resolver.mavenLocal,
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.2.8
sbt.version=1.3.7
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots")
)

addSbtPlugin("org.scala-exercises" % "sbt-exercise" % "0.5.0-SNAPSHOT")
addSbtPlugin("org.scala-exercises" % "sbt-exercise" % "0.6.0-SNAPSHOT")
addSbtPlugin("com.47deg" % "sbt-org-policies" % "0.12.0-M3")
4 changes: 2 additions & 2 deletions src/main/scala/scalatutorial/sections/FunctionalLoops.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,10 @@ object FunctionalLoops extends ScalaTutorialSection {
*
* Complete the following method definition that computes the factorial of a number:
*/
def factorialExercise(res0: Int, res1: Int, res2: Int): Unit = {
def factorialExercise(res0: Int, res1: Int): Unit = {
def factorial(n: Int): Int =
if (n == res0) res1
else factorial(n - res2) * n
else factorial(n - 1) * n

factorial(3) shouldBe 6
factorial(4) shouldBe 24
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ object HigherOrderFunctions extends ScalaTutorialSection {
def sum(f: Int => Int, a: Int, b: Int): Int = {
def loop(x: Int, acc: Int): Int =
if (x > b) acc
else loop(x + res0, acc + f(x))
loop(a, res1)
else loop(x + 1, acc + f(x))
loop(a, res0)
}
sum(x => x, 1, 10) shouldBe 55
sum(x => x, 1, res1) shouldBe 55
}
}
137 changes: 67 additions & 70 deletions src/main/scala/scalatutorial/sections/LazyEvaluation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,29 @@ object LazyEvaluation extends ScalaTutorialSection {
* - Avoid computing the tail of a sequence until it is needed for the evaluation
* result (which might be never)
*
* This idea is implemented in a new class, the `Stream`.
* This idea is implemented in a new class, the `LazyList`.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this needs a good rewording

*
* Streams are similar to lists, but their tail is evaluated only ''on demand''.
* LazyLists are similar to lists, but their elements are evaluated only ''on demand''.
*
* = Defining Streams =
* = Defining LazyLists =
*
* Streams are defined from a constant `Stream.empty` and a constructor `Stream.cons`.
* LazyLists are defined from a constructor `LazyList.cons`.
*
* For instance,
*
* {{{
* val xs = Stream.cons(1, Stream.cons(2, Stream.empty))
* val xs = LazyList.cons(1, LazyList.cons(2, LazyList.empty))
* }}}
*
* = Stream Ranges =
* = LazyList Ranges =
*
* Let's try to write a function that returns a `Stream` representing a range of numbers
* Let's try to write a function that returns a `LazyList` representing a range of numbers
* between `lo` and `hi`:
*
* {{{
* def streamRange(lo: Int, hi: Int): Stream[Int] =
* if (lo >= hi) Stream.empty
* else Stream.cons(lo, streamRange(lo + 1, hi))
* def llRange(lo: Int, hi: Int): LazyList[Int] =
* if (lo >= hi) LazyList.empty
* else LazyList.cons(lo, llRange(lo + 1, hi))
* }}}
*
* Compare to the same function that produces a list:
Expand All @@ -79,113 +79,122 @@ object LazyEvaluation extends ScalaTutorialSection {
* The functions have almost identical structure yet they evaluate quite differently.
*
* - `listRange(start, end)` will produce a list with `end - start` elements and return it.
* - `streamRange(start, end)` returns a single object of type `Stream` with `start` as head element.
* - `llRange(start, end)` returns a single object of type `LazyList` with `start` as head element.
* - The other elements are only computed when they are needed, where
* “needed” means that someone calls `tail` on the stream.
*
* = Methods on Streams =
* = Methods on LazyLists =
*
* `Stream` supports almost all methods of `List`.
* `LazyList` supports almost all methods of `List`.
*
* For instance, to find the second prime number between 1000 and 10000:
*
* {{{
* (streamRange(1000, 10000) filter isPrime)(1)
* (llRange(1000, 10000) filter isPrime)(1)
* }}}
*
* The one major exception is `::`.
*
* `x :: xs` always produces a list, never a stream.
* `x :: xs` always produces a list, never a lazy list.
*
* There is however an alternative operator `#::` which produces a stream.
* There is however an alternative operator `#::` which produces a lazy list.
*
* {{{
* x #:: xs == Stream.cons(x, xs)
* x #:: xs == LazyList.cons(x, xs)
* }}}
*
* `#::` can be used in expressions as well as patterns.
*
* = Implementation of Streams =
* = Implementation of LazyLists =
*
* The implementation of streams is quite close to the one of lists.
* The implementation of lazy lists is quite close to the one of lists.
*
* Here's the trait `Stream`:
* Here's the class `LazyList`:
*
* {{{
* trait Stream[+T] extends Seq[T] {
* def isEmpty: Boolean
* def head: T
* def tail: Stream[T]
*
* final class LazyList[+A] ... extends ... {
* override def isEmpty: Boolean = ...
* override def head: A = ...
* override def tail: LazyList[A] = ...
* ...
* }
* }}}
*
* As for lists, all other methods can be defined in terms of these three.
*
* Concrete implementations of streams are defined in the `Stream` companion object.
* Concrete implementations of streams are defined in the `LazyList.State` companion object.
* Here's a first draft:
*
* {{{
* object Stream {
* def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
* def isEmpty = false
* def head = hd
* def tail = tl
* override def toString = "Stream(" + hd + ", ?)"
* }
* val empty = new Stream[Nothing] {
* def isEmpty = true
* def head = throw new NoSuchElementException("empty.head")
* def tail = throw new NoSuchElementException("empty.tail")
* override def toString = "Stream()"
* private object State {
* object Empty extends State[Nothing] {
* def head: Nothing = throw new NoSuchElementException("head of empty lazy list")
* def tail: LazyList[Nothing] = throw new UnsupportedOperationException("tail of empty lazy list")
* }
*
* final class Cons[A](val head: A, val tail: LazyList[A]) extends State[A]
* }
* }}}
*
* The only important difference between the implementations of `List` and `Stream`
* concern `tl`, the second parameter of `Stream.cons`.
* The only important difference between the implementations of `List` and `LazyList`
* concern `tail`, the second parameter of `LazyList.cons`.
*
* For streams, this is a by-name parameter: the type of `tl` starts with `=>`. In such
* For lazy lists, this is a by-name parameter: the type of `tail` starts with `=>`. In such
* a case, this parameter is evaluated by following the rules of the call-by-name model.
*
* That's why the second argument to `Stream.cons` is not evaluated at the point of call.
* That's why the second argument to `LazyList.cons` is not evaluated at the point of call.
*
* Instead, it will be evaluated each time someone calls `tail` on a `Stream` object.
* Instead, it will be evaluated each time someone calls `tail` on a `LazyList` object.
*
* The other stream methods are implemented analogously to their list counterparts.
* In Scala 2.13, LazyList (previously Stream) became fully lazy from head to tail. To make it possible,
* methods (`filter`, `flatMap`...) are implemented in a way where the head is not being evaluated if is
* not explicitly indicated.
*
* For instance, here's `filter`:
*
* {{{
* class Stream[+T] {
* …
* def filter(p: T => Boolean): Stream[T] =
* if (isEmpty) this
* else if (p(head)) cons(head, tail.filter(p))
* else tail.filter(p)
* object LazyList extends SeqFactory[LazyList] {
* ...
* private def filterImpl[A](ll: LazyList[A], p: A => Boolean, isFlipped: Boolean): LazyList[A] = {
* // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD
* var restRef = ll // val restRef = new ObjectRef(ll)
* newLL {
* var elem: A = null.asInstanceOf[A]
* var found = false
* var rest = restRef // var rest = restRef.elem
* while (!found && !rest.isEmpty) {
* elem = rest.head
* found = p(elem) != isFlipped
* rest = rest.tail
* restRef = rest // restRef.elem = rest
* }
* if (found) sCons(elem, filterImpl(rest, p, isFlipped)) else State.Empty
* }
* }
* }}}
*
* = Exercise =
*
* Consider the following modification of `streamRange`. When you write
* `streamRange(1, 10).take(3).toList` what is the value of `rec`?
* Consider the following modification of `llRange`. When you write
* `llRange(1, 10).take(3).toList` what is the value of `rec`?
*
* Be careful, head is evaluating too!
*/
def streamRangeExercise(res0: Int): Unit = {
def llRangeExercise(res0: Int): Unit = {
var rec = 0
def streamRange(lo: Int, hi: Int): Stream[Int] = {
def llRange(lo: Int, hi: Int): LazyList[Int] = {
rec = rec + 1
if (lo >= hi) Stream.empty
else Stream.cons(lo, streamRange(lo + 1, hi))
if (lo >= hi) LazyList.empty
else LazyList.cons(lo, llRange(lo + 1, hi))
}
streamRange(1, 10).take(3).toList
llRange(1, 10).take(3).toList
rec shouldBe res0
}

/**
* = Lazy Evaluation =
*
* The proposed `Stream` implementation suffers from a serious potential performance
* The proposed `LazyList` implementation suffers from a serious potential performance
* problem: If `tail` is called several times, the corresponding stream
* will be recomputed each time.
*
Expand All @@ -210,18 +219,6 @@ object LazyEvaluation extends ScalaTutorialSection {
* lazy val x = expr
* }}}
*
* = Lazy Vals and Streams =
*
* Using a lazy value for `tail`, `Stream.cons` can be implemented more efficiently:
*
* {{{
* def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] {
* def head = hd
* lazy val tail = tl
* …
* }
* }}}
*
* == Exercise ==
*/
def lazyVal(res0: String): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ object StandardLibrary extends ScalaTutorialSection {
def triple(x: Int): Int = 3 * x

def tripleEither(x: Either[String, Int]): Either[String, Int] =
x.right.map(triple)
x.map(triple)

tripleEither(Right(1)) shouldBe res0
tripleEither(Left("not a number")) shouldBe res1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ object SyntacticConveniences extends ScalaTutorialSection {
*/
def repeatedParameters(res0: Double): Unit = {
def average(x: Int, xs: Int*): Double =
(x :: xs.to[List]).sum.toDouble / (xs.size + 1)
(x :: xs.to(List)).sum.toDouble / (xs.size + 1)

average(1) shouldBe 1.0
average(1, 2) shouldBe 1.5
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/scalatutorial/sections/TailRecursion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ object TailRecursion extends ScalaTutorialSection {
*
* Complete the following definition of a tail-recursive version of `factorial`:
*/
def tailRecFactorial(res0: Int, res1: Int, res2: Int): Unit = {
def tailRecFactorial(res0: Int, res1: Int): Unit = {
def factorial(n: Int): Int = {
@tailrec
def iter(x: Int, result: Int): Int =
if (x == res0) result
else iter(x - res1, result * x)
else iter(x - 1, result * x)

iter(n, res2)
iter(n, res1)
}

factorial(3) shouldBe 6
Expand Down
28 changes: 28 additions & 0 deletions src/test/scala/scalatutorial/sections/FunctionalLoopsSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* scala-exercises - exercises-scalatutorial
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
*
*/

package scalatutorial.sections

import org.scalacheck.{Arbitrary, Gen}
//import org.scalacheck.ScalacheckShapeless._
import org.scalaexercises.Test
import org.scalatest.refspec.RefSpec
import org.scalatestplus.scalacheck.Checkers
import shapeless._

class FunctionalLoopsSpec extends RefSpec with Checkers {

implicit val arb: Arbitrary[Int :: Int :: HNil] = Arbitrary {
for {
num1 <- Gen.choose(-10, 0)
num2 <- Gen.choose(0, 10)
} yield num1 :: num2 :: HNil
}

def `factorial exercise with recursion`(): Unit =
check(Test.testSuccess(FunctionalLoops.factorialExercise _, 0 :: 1 :: HNil))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* scala-exercises - exercises-scalatutorial
* Copyright (C) 2015-2019 47 Degrees, LLC. <http://www.47deg.com>
*
*/

package scalatutorial.sections

//import org.scalacheck.ScalacheckShapeless._
import org.scalacheck.{Arbitrary, Gen}
import org.scalaexercises.Test
import org.scalatest.refspec.RefSpec
import org.scalatestplus.scalacheck.Checkers
import shapeless.{::, HNil}

class HigherOrderFunctionsSpec extends RefSpec with Checkers {

implicit val arb: Arbitrary[Int :: Int :: HNil] = Arbitrary {
for {
num1 <- Gen.posNum[Int]
num2 <- Gen.posNum[Int]
} yield num1 :: num2 :: HNil
}

def `check tail recursive sum function`(): Unit =
check(Test.testSuccess(HigherOrderFunctions.tailRecSum _, 0 :: 10 :: HNil))

}
Loading