From de88de6afb631e8bf3f3decbebfe9abd0ddbdfb0 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Mon, 14 Nov 2016 16:46:16 +0100 Subject: [PATCH 01/19] Rename library to scala-tutorial --- build.sbt | 6 ++--- src/main/resources/scala-tutorial.svg | 23 +++++++++++++++++++ .../fpprincipleslib/FpPrinciplesLibrary.scala | 17 -------------- .../FunctionsAndEvaluationSection.scala | 2 +- .../scalatutorial/ScalaTutorialLibrary.scala | 17 ++++++++++++++ .../FunctionsAndEvaluationSpec.scala | 2 +- 6 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 src/main/resources/scala-tutorial.svg delete mode 100644 src/main/scala/fpprincipleslib/FpPrinciplesLibrary.scala rename src/main/scala/{fpprincipleslib => scalatutorial}/FunctionsAndEvaluationSection.scala (97%) create mode 100644 src/main/scala/scalatutorial/ScalaTutorialLibrary.scala rename src/test/scala/{fpprincipleslib => scalatutorial}/FunctionsAndEvaluationSpec.scala (95%) diff --git a/build.sbt b/build.sbt index 62b8f996..ef1b5912 100644 --- a/build.sbt +++ b/build.sbt @@ -1,10 +1,10 @@ -lazy val template = (project in file(".")) +lazy val `scala-tutorial` = (project in file(".")) .enablePlugins(ExerciseCompilerPlugin) .settings( organization := "org.scala-exercises", - name := "exercises-fpprinciples", + name := "exercises-scalatutorial", scalaVersion := "2.11.8", - version := "0.3.1-SNAPSHOT", + version := "0.3.0-SNAPSHOT", resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases") diff --git a/src/main/resources/scala-tutorial.svg b/src/main/resources/scala-tutorial.svg new file mode 100644 index 00000000..837af7c7 --- /dev/null +++ b/src/main/resources/scala-tutorial.svg @@ -0,0 +1,23 @@ + + + + std_lib + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/scala/fpprincipleslib/FpPrinciplesLibrary.scala b/src/main/scala/fpprincipleslib/FpPrinciplesLibrary.scala deleted file mode 100644 index 511295e7..00000000 --- a/src/main/scala/fpprincipleslib/FpPrinciplesLibrary.scala +++ /dev/null @@ -1,17 +0,0 @@ -package fpprincipleslib -import org.scalaexercises.definitions._ - -/** Exercises for the "Functional Programming Principles in Scala", part of the FP in Scala specialized program by EPFL. - * - * @param name fp_principles - */ -object FpPrinciplesLibrary extends Library { - override def owner = "scala-exercises" - override def repository = "exercises-fpprinciples" - override def color = Some("#585858") - override def logoPath = "fpprinciples" - - override def sections = List( - FunctionsAndEvaluationSection - ) -} diff --git a/src/main/scala/fpprincipleslib/FunctionsAndEvaluationSection.scala b/src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala similarity index 97% rename from src/main/scala/fpprincipleslib/FunctionsAndEvaluationSection.scala rename to src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala index 926b7e07..a3af4a4a 100644 --- a/src/main/scala/fpprincipleslib/FunctionsAndEvaluationSection.scala +++ b/src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala @@ -1,4 +1,4 @@ -package fpprincipleslib +package scalatutorial import org.scalatest._ import org.scalaexercises.definitions._ diff --git a/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala b/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala new file mode 100644 index 00000000..aec5dab3 --- /dev/null +++ b/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala @@ -0,0 +1,17 @@ +package scalatutorial +import org.scalaexercises.definitions._ + +/** Quickly learn Scala through an interactive tutorial. + * + * @param name scala_tutorial + */ +object ScalaTutorialLibrary extends Library { + override def owner = "scala-exercises" + override def repository = "exercises-fpprinciples" + override def color = Some("#224951") + override def logoPath = "scala-tutorial" + + override def sections = List( + FunctionsAndEvaluationSection + ) +} diff --git a/src/test/scala/fpprincipleslib/FunctionsAndEvaluationSpec.scala b/src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala similarity index 95% rename from src/test/scala/fpprincipleslib/FunctionsAndEvaluationSpec.scala rename to src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala index f9b2c9de..0d37b6ae 100644 --- a/src/test/scala/fpprincipleslib/FunctionsAndEvaluationSpec.scala +++ b/src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala @@ -1,4 +1,4 @@ -package fpprincipleslib +package scalatutorial import org.scalacheck.Shapeless._ import org.scalaexercises.Test From 81b6abeb8d3496fb58d35d7b8aab336f08876d55 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Tue, 15 Nov 2016 18:00:02 +0100 Subject: [PATCH 02/19] Add tutorial structure. Add the first two sections. --- .../FunctionsAndEvaluationSection.scala | 30 --- .../scala/scalatutorial/ScalaTutorial.scala | 34 ++++ .../scalatutorial/ScalaTutorialLibrary.scala | 17 -- .../sections/ClassesVsCaseClasses.scala | 6 + .../sections/DefinitionsAndEvaluation.scala | 171 +++++++++++++++++ .../sections/FunctionalLoops.scala | 6 + .../sections/HigherOrderFunctions.scala | 6 + .../sections/ImperativeProgramming.scala | 6 + .../sections/LazyEvaluation.scala | 6 + .../sections/LexicalScopes.scala | 6 + .../sections/ObjectOrientedProgramming.scala | 6 + .../sections/PolymorphicTypes.scala | 6 + .../sections/ScalaTutorialSection.scala | 6 + .../sections/StandardLibrary.scala | 6 + .../sections/StructuringInformation.scala | 6 + .../sections/SyntacticConveniences.scala | 6 + .../sections/TailRecursion.scala | 6 + .../sections/TermsAndTypes.scala | 174 ++++++++++++++++++ .../scalatutorial/sections/TypeClasses.scala | 6 + .../FunctionsAndEvaluationSpec.scala | 17 -- .../DefinitionsAndEvaluationSpec.scala | 19 ++ .../sections/TermsAndTypesSpec.scala | 23 +++ 22 files changed, 505 insertions(+), 64 deletions(-) delete mode 100644 src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala create mode 100644 src/main/scala/scalatutorial/ScalaTutorial.scala delete mode 100644 src/main/scala/scalatutorial/ScalaTutorialLibrary.scala create mode 100644 src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala create mode 100644 src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala create mode 100644 src/main/scala/scalatutorial/sections/FunctionalLoops.scala create mode 100644 src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala create mode 100644 src/main/scala/scalatutorial/sections/ImperativeProgramming.scala create mode 100644 src/main/scala/scalatutorial/sections/LazyEvaluation.scala create mode 100644 src/main/scala/scalatutorial/sections/LexicalScopes.scala create mode 100644 src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala create mode 100644 src/main/scala/scalatutorial/sections/PolymorphicTypes.scala create mode 100644 src/main/scala/scalatutorial/sections/ScalaTutorialSection.scala create mode 100644 src/main/scala/scalatutorial/sections/StandardLibrary.scala create mode 100644 src/main/scala/scalatutorial/sections/StructuringInformation.scala create mode 100644 src/main/scala/scalatutorial/sections/SyntacticConveniences.scala create mode 100644 src/main/scala/scalatutorial/sections/TailRecursion.scala create mode 100644 src/main/scala/scalatutorial/sections/TermsAndTypes.scala create mode 100644 src/main/scala/scalatutorial/sections/TypeClasses.scala delete mode 100644 src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala create mode 100644 src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala create mode 100644 src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala diff --git a/src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala b/src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala deleted file mode 100644 index a3af4a4a..00000000 --- a/src/main/scala/scalatutorial/FunctionsAndEvaluationSection.scala +++ /dev/null @@ -1,30 +0,0 @@ -package scalatutorial - -import org.scalatest._ -import org.scalaexercises.definitions._ - -/** @param name functions_and_evaluation - */ - -object FunctionsAndEvaluationSection extends FlatSpec with Matchers with Section { - /** = Exercise block title = - * - * Text describing background about the exercise, can be as long as needed. - * - * {{{ - * // Scala code blocks can also be added to enhance your documentation. - * }}} - * - * Also, documentation can be broken in as many paragraphs as necessary. - */ - def functionAssert(res0: Boolean): Unit = { - true shouldBe res0 - } - - /** And obviously you can add as many documentation and exercises as you need - * to make your point ;-). - */ - def functionFalseAssert(res0: Boolean): Unit = { - false shouldBe res0 - } -} diff --git a/src/main/scala/scalatutorial/ScalaTutorial.scala b/src/main/scala/scalatutorial/ScalaTutorial.scala new file mode 100644 index 00000000..130c42c1 --- /dev/null +++ b/src/main/scala/scalatutorial/ScalaTutorial.scala @@ -0,0 +1,34 @@ +package scalatutorial + +import org.scalaexercises.definitions.Library + +import sections._ + +/** Quickly learn Scala through an interactive tutorial. + * + * @param name scala_tutorial + */ +object ScalaTutorial extends Library { + val owner = "scala-exercises" + val repository = "exercises-fpprinciples" + override val color = Some("#224951") + val logoPath = "scala-tutorial" + + val sections = List( + TermsAndTypes, + DefinitionsAndEvaluation, + FunctionalLoops, + LexicalScopes, + TailRecursion, + StructuringInformation, + HigherOrderFunctions, + StandardLibrary, + SyntacticConveniences, + ObjectOrientedProgramming, + ImperativeProgramming, + ClassesVsCaseClasses, + LazyEvaluation, + PolymorphicTypes, + TypeClasses + ) +} diff --git a/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala b/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala deleted file mode 100644 index aec5dab3..00000000 --- a/src/main/scala/scalatutorial/ScalaTutorialLibrary.scala +++ /dev/null @@ -1,17 +0,0 @@ -package scalatutorial -import org.scalaexercises.definitions._ - -/** Quickly learn Scala through an interactive tutorial. - * - * @param name scala_tutorial - */ -object ScalaTutorialLibrary extends Library { - override def owner = "scala-exercises" - override def repository = "exercises-fpprinciples" - override def color = Some("#224951") - override def logoPath = "scala-tutorial" - - override def sections = List( - FunctionsAndEvaluationSection - ) -} diff --git a/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala b/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala new file mode 100644 index 00000000..a934a237 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name classes_vs_case_classes */ +object ClassesVsCaseClasses extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala new file mode 100644 index 00000000..4effbe3f --- /dev/null +++ b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala @@ -0,0 +1,171 @@ +package scalatutorial.sections + +/** @param name definitions_and_evaluation */ +object DefinitionsAndEvaluation extends ScalaTutorialSection { + + /** + * = Naming Things = + * + * Consider the following program that computes the area of a disc + * whose radius is `10`: + * + * {{{ + * 3.14159 * 10 * 10 + * }}} + * + * To make complex expressions more readable we can give meaningful names to + * intermediate expressions: + * + * {{{ + * val radius = 10 + * val pi = 3.14159 + * + * pi * radius * radius + * }}} + * + * Besides making the last expression more readable it also allows us to + * not repeat the actual value of the radius. + * + * = Evaluation = + * + * A name is evaluated by replacing it with the right hand side of its definition + * + * == Example == + * + * Here are the evaluation steps of the above expression: + * + * {{{ + * pi * radius * radius + * 3.14159 * radius * radius + * 3.14159 * 10 * radius + * 31.4159 * radius + * 31.4159 * 10 + * 314.159 + * }}} + * + * = Methods = + * + * Definitions can have parameters. For instance: + */ + def square(x: Double) = x * x + + /** + * And then you can ''call'' a method as follows: + */ + def usingSquare(res0: Double): Unit = { + square(3.0) shouldBe res0 + } + + /** + * Let’s define a method that computes the area of a disc, given its radius: + */ + def areaExercise(res0: Double): Unit = { + def area(radius: Double): Double = 3.14159 * square(radius) + + area(10) shouldBe res0 + } + + /** + * = Multiple Parameters = + * + * Separate several parameters with commas: + */ + def sumOfSquares(x: Double, y: Double) = square(x) + square(y) + +/** + * = Parameters and Return Types = + * + * Function parameters come with their type, which is given after a colon + * + * {{{ + * def power(x: Double, y: Int): Double = ... + * }}} + * + * If a return type is given, it follows the parameter list. + * + * = Evaluation of Function Applications = + * + * Applications of parametrized functions are evaluated in a similar way as + * operators: + * + * 1. Evaluate all function arguments, from left to right + * 1. Replace the function application by the function's right-hand side, and, at the same time + * 1. Replace the formal parameters of the function by the actual arguments. + * + * == Example == + * + * {{{ + * sumOfSquares(3, 2+2) + * sumOfSquares(3, 4) + * square(3) + square(4) + * 3 * 3 + square(4) + * 9 + square(4) + * 9 + 4 * 4 + * 9 + 16 + * 25 + * }}} + * + * = The substitution model = + * + * This scheme of expression evaluation is called the ''substitution model''. + * + * The idea underlying this model is that all evaluation does is ''reduce + * an expression to a value''. + * + * It can be applied to all expressions, as long as they have no side effects. + * + * The substitution model is formalized in the λ-calculus, which gives + * a foundation for functional programming. + * + * = Termination = + * + * Does every expression reduce to a value (in a finite number of steps)? + * + * No. Here is a counter-example: + * + * {{{ + * def loop: Int = loop + * + * loop + * }}} + * + * = Changing the evaluation strategy = + * + * The interpreter reduces function arguments to values before rewriting the + * function application. + * + * One could alternatively apply the function to unreduced arguments. + * + * For instance: + * + * {{{ + * sumOfSquares(3, 2+2) + * square(3) + square(2+2) + * 3 * 3 + square(2+2) + * 9 + square(2+2) + * 9 + (2+2) * (2+2) + * 9 + 4 * (2+2) + * 9 + 4 * 4 + * 25 + * }}} + * + * = Call-by-name and call-by-value = + * + * The first evaluation strategy is known as ''call-by-value'', + * the second is is known as ''call-by-name''. + * + * Both strategies reduce to the same final values + * as long as + * + * - the reduced expression consists of pure functions, and + * - both evaluations terminate. + * + * Call-by-value has the advantage that it evaluates every function argument + * only once. + * + * Call-by-name has the advantage that a function argument is not evaluated if the + * corresponding parameter is unused in the evaluation of the function body. + */ + def nothing(): Unit = () + +} diff --git a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala new file mode 100644 index 00000000..3073ce92 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name functional_loops */ +object FunctionalLoops extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala b/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala new file mode 100644 index 00000000..55f6d21d --- /dev/null +++ b/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name higher_order_functions */ +object HigherOrderFunctions extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala new file mode 100644 index 00000000..1c88515b --- /dev/null +++ b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name imperative_programming */ +object ImperativeProgramming extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/LazyEvaluation.scala b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala new file mode 100644 index 00000000..40331569 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name lazy_evaluation */ +object LazyEvaluation extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala new file mode 100644 index 00000000..fcd7a84c --- /dev/null +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name lexical_scopes */ +object LexicalScopes extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala new file mode 100644 index 00000000..6f94a840 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name object_oriented_programming */ +object ObjectOrientedProgramming extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala new file mode 100644 index 00000000..eed9ab69 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name polymorphic_types */ +object PolymorphicTypes extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/ScalaTutorialSection.scala b/src/main/scala/scalatutorial/sections/ScalaTutorialSection.scala new file mode 100644 index 00000000..d2b97d60 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/ScalaTutorialSection.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +import org.scalaexercises.definitions.Section +import org.scalatest.{FlatSpec, Matchers} + +trait ScalaTutorialSection extends FlatSpec with Matchers with Section diff --git a/src/main/scala/scalatutorial/sections/StandardLibrary.scala b/src/main/scala/scalatutorial/sections/StandardLibrary.scala new file mode 100644 index 00000000..19807884 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/StandardLibrary.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name standard_library */ +object StandardLibrary extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/StructuringInformation.scala b/src/main/scala/scalatutorial/sections/StructuringInformation.scala new file mode 100644 index 00000000..f123a14d --- /dev/null +++ b/src/main/scala/scalatutorial/sections/StructuringInformation.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name structuring_information */ +object StructuringInformation extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala new file mode 100644 index 00000000..8f19b2ff --- /dev/null +++ b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name syntactic_conveniences */ +object SyntacticConveniences extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/TailRecursion.scala b/src/main/scala/scalatutorial/sections/TailRecursion.scala new file mode 100644 index 00000000..979ba811 --- /dev/null +++ b/src/main/scala/scalatutorial/sections/TailRecursion.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name tail_recursion */ +object TailRecursion extends ScalaTutorialSection { + +} diff --git a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala new file mode 100644 index 00000000..391a20ba --- /dev/null +++ b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala @@ -0,0 +1,174 @@ +package scalatutorial.sections + +/** @param name terms_and_types */ +object TermsAndTypes extends ScalaTutorialSection { + + /** + * = Scala Tutorial = + * + * The following set of sections provides a quick tutorial on the Scala language. + * + * The contents is based on the MOOCS [[https://www.coursera.org/learn/progfun1/home Functional Programming Principles in Scala]] + * and [[https://www.coursera.org/learn/progfun2/home Functional Program Design in Scala]]. + * + * = Elements of Programming = + * + * Programming languages give programmers ways to express computations. + * + * Every non-trivial programming language provides: + * + * - primitive expressions representing the simplest elements ; + * - ways to ''combine'' expressions ; + * - ways to ''abstract'' expressions, which introduce a name for an expression by which it can then be referred to. + * + * = Primitive Expressions = + * + * Here are some examples of ''primitive expressions'': + * + * - The number “1”: + * + * {{{ + * 1 + * }}} + * + * - The boolean value “true”: + * + * {{{ + * true + * }}} + * + * - The text “Hello, Scala!”: + * + * {{{ + * "Hello, Scala!" + * }}} + * + * (Note the usage of double quotes, `"`). + * + * = Compound Expressions = + * + * More complex expressions can be expressed by ''combining'' simpler expressions + * using ''operators''. They can therefore express more complex computations: + * + * - How many is one plus two? + * + * {{{ + * 1 + 2 + * }}} + * + * - What is the result of the concatenation of the texts “Hello, ” and “Scala!”? + * + * {{{ + * "Hello, " ++ "Scala!" + * }}} + * + * = Evaluation = + * + * A non-primitive expression is evaluated as follows. + * + * 1. Take the leftmost operator + * 1. Evaluate its operands (left before right) + * 1. Apply the operator to the operands + * + * The evaluation process stops once it results in a value. + * + * == Example == + * + * Here is the evaluation of an arithmetic expression: + * + * {{{ + * (1 + 2) * 3 + * 3 * 3 + * 9 + * }}} + * + */ + def evaluation(res0: Int, res1: String): Unit = { + 1 + 2 shouldBe res0 + "Hello, " ++ "Scala!" shouldBe res1 + } + + /** + * = Method Calls = + * + * Another way to make complex expressions out of simpler expressions is to call + * ''methods'' on expressions: + * + * - What is the size of the text “Hello, Scala!”? + * + * {{{ + * "Hello, Scala!".size + * }}} + * + * Methods are ''applied'' on expressions using the ''dot notation''. + * + * The object on which the method is applied is named the ''target object''. + * + * - What is the range of numbers between 1 and 10? + * + * {{{ + * 1.to(10) + * }}} + * + * Methods can have ''parameters''. They are supplied between parentheses. + * + * In the below examples, the `abs` method returns the absolute value of a + * number, and the `toUpperCase` method returns the target `String` in + * upper case. + */ + def methods(res0: Int, res1: String): Unit = { + "Hello, Scala!".toUpperCase shouldBe res1 + -42.abs shouldBe res0 + } + + /** + * = Operators Are Methods = + * + * Actually, operators are just methods with symbolic names: + * + * {{{ + * 3 + 2 == 3.+(2) + * }}} + * + * The ''infix syntax'' allows you to omit the dot and the parentheses. + * + * The infix syntax can also be used with regular methods: + * + * {{{ + * 1.to(10) == 1 to 10 + * }}} + * + * = Values and Types = + * + * Expressions have a ''value'' and a ''type''. The evaluation model + * defines how to get a value out of an expression. Types classify values. + * + * Both `0` and `1` are numbers, their type is `Int`. + * + * `"foo"` and `"bar"` are text, their type is `String`. + * + * = Static Typing = + * + * The Scala compiler statically checks that you don’t combine incompatible + * expressions. + * + * Fill the following blank with values whose type is + * different from `Int` and see the result: + */ + def staticTyping(res0: Int): Unit = { + 1 to res0 + } + + /** + * = Common Types = + * + * - `Int`: 32-bit integers + * - `Double`: 64-bit floating point numbers + * - `Boolean`: boolean values + * - `String`: text + * + * Note that type names always begin with an upper case. + */ + def nothing(): Unit = () + +} diff --git a/src/main/scala/scalatutorial/sections/TypeClasses.scala b/src/main/scala/scalatutorial/sections/TypeClasses.scala new file mode 100644 index 00000000..8f5a73cc --- /dev/null +++ b/src/main/scala/scalatutorial/sections/TypeClasses.scala @@ -0,0 +1,6 @@ +package scalatutorial.sections + +/** @param name type_classes */ +object TypeClasses extends ScalaTutorialSection { + +} diff --git a/src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala b/src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala deleted file mode 100644 index 0d37b6ae..00000000 --- a/src/test/scala/scalatutorial/FunctionsAndEvaluationSpec.scala +++ /dev/null @@ -1,17 +0,0 @@ -package scalatutorial - -import org.scalacheck.Shapeless._ -import org.scalaexercises.Test -import org.scalatest.Spec -import org.scalatest.prop.Checkers -import shapeless.HNil - -class FunctionsAndEvaluationSectionSpec extends Spec with Checkers { - def `function asserts` = { - check(Test.testSuccess(FunctionsAndEvaluationSection.functionAssert _, true :: HNil)) - } - - def `function false asserts` = { - check(Test.testSuccess(FunctionsAndEvaluationSection.functionFalseAssert _, false :: HNil)) - } -} \ No newline at end of file diff --git a/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala b/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala new file mode 100644 index 00000000..e218dd96 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala @@ -0,0 +1,19 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class DefinitionsAndEvaluationSpec extends Spec with Checkers { + + def `check using square`: Unit = { + check(Test.testSuccess(DefinitionsAndEvaluation.usingSquare _, 9.0 :: HNil)) + } + + def `check area exercise`: Unit = { + check(Test.testSuccess(DefinitionsAndEvaluation.areaExercise _, 314.159 :: HNil)) + } + +} diff --git a/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala b/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala new file mode 100644 index 00000000..55eece22 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala @@ -0,0 +1,23 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class TermsAndTypesSpec extends Spec with Checkers { + + def `check evaluation`: Unit = { + check(Test.testSuccess(TermsAndTypes.evaluation _, 3 :: "Hello, Scala!" :: HNil)) + } + + def `check methods`: Unit = { + check(Test.testSuccess(TermsAndTypes.methods _, 42 :: "HELLO, SCALA!" :: HNil)) + } + +// def `static typing`: Unit = { +// check(Test.testSuccess(TermsAndTypes.staticTyping _, 10 :: HNil)) +// } + +} From 554f908cffde8cf46ba7f698b10d8ffe156d132d Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 16 Nov 2016 10:19:37 +0100 Subject: [PATCH 03/19] Add FunctionalLoops --- .../sections/DefinitionsAndEvaluation.scala | 243 +++++++++++------- .../sections/FunctionalLoops.scala | 123 +++++++++ .../sections/LexicalScopes.scala | 4 + .../sections/FunctionalLoopsSpec.scala | 16 ++ 4 files changed, 286 insertions(+), 100 deletions(-) create mode 100644 src/test/scala/scalatutorial/sections/FunctionalLoopsSpec.scala diff --git a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala index 4effbe3f..2a8d31df 100644 --- a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala +++ b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala @@ -46,10 +46,11 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { * = Methods = * * Definitions can have parameters. For instance: - */ - def square(x: Double) = x * x - - /** + * + * {{{ + * def square(x: Double) = x * x + * }}} + * * And then you can ''call'' a method as follows: */ def usingSquare(res0: Double): Unit = { @@ -69,103 +70,145 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { * = Multiple Parameters = * * Separate several parameters with commas: + * + * {{{ + * def sumOfSquares(x: Double, y: Double) = square(x) + square(y) + * }}} + * + * = Parameters and Return Types = + * + * Function parameters come with their type, which is given after a colon + * + * {{{ + * def power(x: Double, y: Int): Double = ... + * }}} + * + * If a return type is given, it follows the parameter list. + * + * = Val vs Def = + * + * The right hand side of a `def` definition is evaluated on each use. + * + * The right hand side of a `val` definition is evaluated at the point of the definition + * itself. Afterwards, the name refers to the value. + * + * {{{ + * val x = 2 + * val y = square(x) + * }}} + * + * For instance, `y` above refers to `4`, not `square(2)`. + * + * = Evaluation of Function Applications = + * + * Applications of parametrized functions are evaluated in a similar way as + * operators: + * + * 1. Evaluate all function arguments, from left to right + * 1. Replace the function application by the function's right-hand side, and, at the same time + * 1. Replace the formal parameters of the function by the actual arguments. + * + * == Example == + * + * {{{ + * sumOfSquares(3, 2+2) + * sumOfSquares(3, 4) + * square(3) + square(4) + * 3 * 3 + square(4) + * 9 + square(4) + * 9 + 4 * 4 + * 9 + 16 + * 25 + * }}} + * + * = The substitution model = + * + * This scheme of expression evaluation is called the ''substitution model''. + * + * The idea underlying this model is that all evaluation does is ''reduce + * an expression to a value''. + * + * It can be applied to all expressions, as long as they have no side effects. + * + * The substitution model is formalized in the λ-calculus, which gives + * a foundation for functional programming. + * + * = Termination = + * + * Does every expression reduce to a value (in a finite number of steps)? + * + * No. Here is a counter-example: + * + * {{{ + * def loop: Int = loop + * + * loop + * }}} + * + * = Value Definitions and Termination = + * + * The difference between `val` and `def` becomes apparent when the right + * hand side does not terminate. Given + * + * {{{ + * def loop: Int = loop + * }}} + * + * A definition + * + * {{{ + * def x = loop + * }}} + * + * is OK, but a definition + * + * {{{ + * val x = loop + * }}} + * + * will lead to an infinite loop. + * + * = Changing the evaluation strategy = + * + * The interpreter reduces function arguments to values before rewriting the + * function application. + * + * One could alternatively apply the function to unreduced arguments. + * + * For instance: + * + * {{{ + * sumOfSquares(3, 2+2) + * square(3) + square(2+2) + * 3 * 3 + square(2+2) + * 9 + square(2+2) + * 9 + (2+2) * (2+2) + * 9 + 4 * (2+2) + * 9 + 4 * 4 + * 25 + * }}} + * + * = Call-by-name and call-by-value = + * + * The first evaluation strategy is known as ''call-by-value'', + * the second is is known as ''call-by-name''. + * + * Both strategies reduce to the same final values + * as long as + * + * - the reduced expression consists of pure functions, and + * - both evaluations terminate. + * + * Call-by-value has the advantage that it evaluates every function argument + * only once. + * + * Call-by-name has the advantage that a function argument is not evaluated if the + * corresponding parameter is unused in the evaluation of the function body. + * + * Scala normally uses call-by-value. */ - def sumOfSquares(x: Double, y: Double) = square(x) + square(y) - -/** - * = Parameters and Return Types = - * - * Function parameters come with their type, which is given after a colon - * - * {{{ - * def power(x: Double, y: Int): Double = ... - * }}} - * - * If a return type is given, it follows the parameter list. - * - * = Evaluation of Function Applications = - * - * Applications of parametrized functions are evaluated in a similar way as - * operators: - * - * 1. Evaluate all function arguments, from left to right - * 1. Replace the function application by the function's right-hand side, and, at the same time - * 1. Replace the formal parameters of the function by the actual arguments. - * - * == Example == - * - * {{{ - * sumOfSquares(3, 2+2) - * sumOfSquares(3, 4) - * square(3) + square(4) - * 3 * 3 + square(4) - * 9 + square(4) - * 9 + 4 * 4 - * 9 + 16 - * 25 - * }}} - * - * = The substitution model = - * - * This scheme of expression evaluation is called the ''substitution model''. - * - * The idea underlying this model is that all evaluation does is ''reduce - * an expression to a value''. - * - * It can be applied to all expressions, as long as they have no side effects. - * - * The substitution model is formalized in the λ-calculus, which gives - * a foundation for functional programming. - * - * = Termination = - * - * Does every expression reduce to a value (in a finite number of steps)? - * - * No. Here is a counter-example: - * - * {{{ - * def loop: Int = loop - * - * loop - * }}} - * - * = Changing the evaluation strategy = - * - * The interpreter reduces function arguments to values before rewriting the - * function application. - * - * One could alternatively apply the function to unreduced arguments. - * - * For instance: - * - * {{{ - * sumOfSquares(3, 2+2) - * square(3) + square(2+2) - * 3 * 3 + square(2+2) - * 9 + square(2+2) - * 9 + (2+2) * (2+2) - * 9 + 4 * (2+2) - * 9 + 4 * 4 - * 25 - * }}} - * - * = Call-by-name and call-by-value = - * - * The first evaluation strategy is known as ''call-by-value'', - * the second is is known as ''call-by-name''. - * - * Both strategies reduce to the same final values - * as long as - * - * - the reduced expression consists of pure functions, and - * - both evaluations terminate. - * - * Call-by-value has the advantage that it evaluates every function argument - * only once. - * - * Call-by-name has the advantage that a function argument is not evaluated if the - * corresponding parameter is unused in the evaluation of the function body. - */ def nothing(): Unit = () + def square(x: Double) = x * x + } diff --git a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala index 3073ce92..63b10f70 100644 --- a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala +++ b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala @@ -3,4 +3,127 @@ package scalatutorial.sections /** @param name functional_loops */ object FunctionalLoops extends ScalaTutorialSection { + /** + * = Conditional Expressions = + * + * To express choosing between two alternatives, Scala + * has a conditional expression `if-else`. + * + * It looks like a `if-else` in Java, but is used for expressions, not statements. + * + * Example: + * + * {{{ + * def abs(x: Int) = if (x >= 0) x else -x + * }}} + * + * `x >= 0` is a ''predicate'', of type `Boolean`. + * + * = Boolean Expressions = + * + * Boolean expressions `b` can be composed of + * + * {{{ + * true false // Constants + * !b // Negation + * b && b // Conjunction + * b || b // Disjunction + * }}} + * + * and of the usual comparison operations: + * {{{ + * e <= e, e >= e, e < e, e > e, e == e, e != e + * }}} + * + * = Rewrite rules for Booleans = + * + * Here are reduction rules for Boolean expressions (`e` is an arbitrary expression): + * + * {{{ + * !true --> false + * !false --> true + * true && e --> e + * false && e --> false + * true || e --> true + * false || e --> e + * }}} + * + * Note that `&&` and `||` do not always need their right operand to be evaluated. + * + * We say, these expressions use “short-circuit evaluation”. + * + * = Computing the Square Root of a Value = + * + * We will define in this section a method + * + * {{{ + * /** Calculates the square root of parameter x */ + * def sqrt(x: Double): Double = ... + * }}} + * + * The classical way to achieve this is by successive approximations using + * Newton's method. + * + * = Method = + * + * To compute `sqrt(x)`: + * + * - Start with an initial _estimate_ `y` (let's pick `y = 1`). + * - Repeatedly improve the estimate by taking the mean of `y` and `x/y`. + * + * Example: + * + * {{{ + * Estimation Quotient Mean + * 1 2 / 1 = 2 1.5 + * 1.5 2 / 1.5 = 1.333 1.4167 + * 1.4167 2 / 1.4167 = 1.4118 1.4142 + * 1.4142 ... ... + * }}} + * + * = Implementation in Scala = + * + * First, we define a method which computes one iteration step + * + * {{{ + * def sqrtIter(guess: Double, x: Double): Double = + * if (isGoodEnough(guess, x)) guess + * else sqrtIter(improve(guess, x), x) + * }}} + * + * Note that `sqrtIter` is ''recursive'', its right-hand side calls itself. + * + * Recursive methods need an explicit return type in Scala. + * + * For non-recursive methods, the return type is optional. + * + * Second, we define a method `improve` to improve an estimate and a test to check for termination: + * + * {{{ + * def improve(guess: Double, x: Double) = + * (guess + x / guess) / 2 + * + * def isGoodEnough(guess: Double, x: Double) = + * abs(guess * guess - x) < 0.001 + * }}} + * + * Third, we define the `sqrt` function: + * + * {{{ + * def sqrt(x: Double) = sqrtIter(1.0, x) + * }}} + * + * = Exercise = + * + * Complete the following method definition that computes the factorial of a number: + */ + def factorialExercise(res0: Int, res1: Int, res2: Int): Unit = { + def factorial(n: Int): Int = + if (n == res0) res1 + else factorial(n - res2) * n + + factorial(3) shouldBe 6 + factorial(4) shouldBe 24 + } + } diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala index fcd7a84c..fc7f6e2f 100644 --- a/src/main/scala/scalatutorial/sections/LexicalScopes.scala +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -3,4 +3,8 @@ package scalatutorial.sections /** @param name lexical_scopes */ object LexicalScopes extends ScalaTutorialSection { + /** + * + */ + def nothing(): Unit = () } diff --git a/src/test/scala/scalatutorial/sections/FunctionalLoopsSpec.scala b/src/test/scala/scalatutorial/sections/FunctionalLoopsSpec.scala new file mode 100644 index 00000000..98a92e6d --- /dev/null +++ b/src/test/scala/scalatutorial/sections/FunctionalLoopsSpec.scala @@ -0,0 +1,16 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class FunctionalLoopsSpec extends Spec with Checkers { + +// Commented because the implementation is not stack-safe +// def `check factorial`: Unit = { +// check(Test.testSuccess(FunctionalLoops.factorialExercise _, 0 :: 1 :: 1 :: HNil)) +// } + +} From d9106dd52f0a35fba28cfde21a8cff48477eb448 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 16 Nov 2016 12:02:28 +0100 Subject: [PATCH 04/19] Start writing the LexicalScopes section --- .../sections/DefinitionsAndEvaluation.scala | 12 +- .../sections/FunctionalLoops.scala | 4 +- .../sections/LexicalScopes.scala | 160 ++++++++++++++++++ 3 files changed, 166 insertions(+), 10 deletions(-) diff --git a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala index 2a8d31df..34ea0856 100644 --- a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala +++ b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala @@ -46,14 +46,10 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { * = Methods = * * Definitions can have parameters. For instance: - * - * {{{ - * def square(x: Double) = x * x - * }}} - * - * And then you can ''call'' a method as follows: */ def usingSquare(res0: Double): Unit = { + def square(x: Double) = x * x + square(3.0) shouldBe res0 } @@ -61,6 +57,8 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { * Let’s define a method that computes the area of a disc, given its radius: */ def areaExercise(res0: Double): Unit = { + def square(x: Double) = x * x + def area(radius: Double): Double = 3.14159 * square(radius) area(10) shouldBe res0 @@ -209,6 +207,4 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { */ def nothing(): Unit = () - def square(x: Double) = x * x - } diff --git a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala index 63b10f70..792da589 100644 --- a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala +++ b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala @@ -68,7 +68,7 @@ object FunctionalLoops extends ScalaTutorialSection { * * To compute `sqrt(x)`: * - * - Start with an initial _estimate_ `y` (let's pick `y = 1`). + * - Start with an initial ''estimate'' `y` (let's pick `y = 1`). * - Repeatedly improve the estimate by taking the mean of `y` and `x/y`. * * Example: @@ -83,7 +83,7 @@ object FunctionalLoops extends ScalaTutorialSection { * * = Implementation in Scala = * - * First, we define a method which computes one iteration step + * First, we define a method which computes one iteration step: * * {{{ * def sqrtIter(guess: Double, x: Double): Double = diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala index fc7f6e2f..f6df8004 100644 --- a/src/main/scala/scalatutorial/sections/LexicalScopes.scala +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -4,7 +4,167 @@ package scalatutorial.sections object LexicalScopes extends ScalaTutorialSection { /** + * = Nested functions = * + * It's good functional programming style to split up a task into many small functions. + * + * But the names of functions like `sqrtIter`, `improve`, and `isGoodEnough` (defined in the + * previous section) matter only for the ''implementation'' of `sqrt`, not for its ''usage''. + * + * Normally we would not like users to access these functions directly. + * + * We can achieve this and at the same time avoid “name-space pollution” by + * putting the auxiliary functions inside `sqrt`. + * + * = The `sqrt` Function, Take 2 = + * + * {{{ + * def sqrt(x: Double) = { + * def sqrtIter(guess: Double, x: Double): Double = + * if (isGoodEnough(guess, x)) guess + * else sqrtIter(improve(guess, x), x) + * + * def improve(guess: Double, x: Double) = + * (guess + x / guess) / 2 + * + * def isGoodEnough(guess: Double, x: Double) = + * abs(square(guess) - x) < 0.001 + * + * sqrtIter(1.0, x) + * } + * }}} + * + * = Blocks in Scala = + * + * - A block is delimited by braces `{ ... }`. + * + * {{{ + * { + * val x = f(3) + * x * x + * } + * }}} + * + * - It contains a sequence of definitions or expressions. + * - The last element of a block is an expression that defines its value. + * - This return expression can be preceded by auxiliary definitions. + * - Blocks are themselves expressions; a block may appear everywhere an expression can. + * + * = Blocks and Visibility = + * + * - The definitions inside a block are only visible from within the block. + * - The definitions inside a block ''shadow'' definitions of the same names + * outside the block. + * + * == Exercise: Scope Rules == + * + * What is the value of `result` in the following program? + * + */ + def scopeRules(res0: Int): Unit = { + val x = 0 + def f(y: Int) = y + 1 + val result = { + val x = f(3) + x * x + } + x + result shouldBe res0 + } + + /** + * = Lexical Scoping = + * + * Definitions of outer blocks are visible inside a block unless they are shadowed. + * + * Therefore, we can simplify `sqrt` by eliminating redundant occurrences of the `x` parameter, which means + * everywhere the same thing: + * + * = The `sqrt` Function, Take 3 = + * + * {{{ + * def sqrt(x: Double) = { + * def sqrtIter(guess: Double): Double = + * if (isGoodEnough(guess)) guess + * else sqrtIter(improve(guess)) + * + * def improve(guess: Double) = + * (guess + x / guess) / 2 + * + * def isGoodEnough(guess: Double) = + * abs(square(guess) - x) < 0.001 + * + * sqrtIter(1.0) + * } + * }}} + * + * = Semicolons = + * + * In Scala, semicolons at the end of lines are in most cases optional. + * + * You could write: + * + * {{{ + * val x = 1; + * }}} + * + * but most people would omit the semicolon. + * + * On the other hand, if there are more than one statements on a line, they need to be + * separated by semicolons: + * + * {{{ + * val y = x + 1; y * y + * }}} + * + * = Semicolons and infix operators = + * + * One issue with Scala's semicolon convention is how to write expressions that span + * several lines. For instance: + * + * {{{ + * someLongExpression + * + someOtherExpression + * }}} + * + * would be interpreted as ''two'' expressions: + * + * {{{ + * someLongExpression; + * + someOtherExpression + * }}} + * + * There are two ways to overcome this problem. + * + * You could write the multi-line expression in parentheses, because semicolons + * are never inserted inside `(...)`: + * + * {{{ + * (someLongExpression + * + someOtherExpression) + * }}} + * + * Or you could write the operator on the first line, because this tells the Scala + * compiler that the expression is not yet finished: + * + * {{{ + * someLongExpression + + * someOtherExpression + * }}} + * + * = Summary = + * + * You have seen simple elements of functional programing in Scala. + * + * - arithmetic and boolean expressions + * - conditional expressions if-else + * - functions with recursion + * - nesting and lexical scope + * + * You have learned the difference between the call-by-name and + * call-by-value evaluation strategies. + * + * You have learned a way to reason about program execution: reduce expressions using + * the substitution model. */ def nothing(): Unit = () } From 680802033028cdd5492f3f36e1bf5926d51242fc Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 16 Nov 2016 13:22:35 +0100 Subject: [PATCH 05/19] Add information about packages and imports --- .../scala/scalatutorial/ScalaTutorial.scala | 2 +- .../sections/FunctionalLoops.scala | 14 ++++ .../sections/LexicalScopes.scala | 80 ++++++++++++++++--- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/main/scala/scalatutorial/ScalaTutorial.scala b/src/main/scala/scalatutorial/ScalaTutorial.scala index 130c42c1..31d37ea0 100644 --- a/src/main/scala/scalatutorial/ScalaTutorial.scala +++ b/src/main/scala/scalatutorial/ScalaTutorial.scala @@ -11,7 +11,7 @@ import sections._ object ScalaTutorial extends Library { val owner = "scala-exercises" val repository = "exercises-fpprinciples" - override val color = Some("#224951") + override val color = Some("#f26527") val logoPath = "scala-tutorial" val sections = List( diff --git a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala index 792da589..3db261f4 100644 --- a/src/main/scala/scalatutorial/sections/FunctionalLoops.scala +++ b/src/main/scala/scalatutorial/sections/FunctionalLoops.scala @@ -113,6 +113,20 @@ object FunctionalLoops extends ScalaTutorialSection { * def sqrt(x: Double) = sqrtIter(1.0, x) * }}} * + * = Summary = + * + * You have seen simple elements of functional programing in Scala. + * + * - arithmetic and boolean expressions + * - conditional expressions if-else + * - functions with recursion + * + * You have learned the difference between the call-by-name and + * call-by-value evaluation strategies. + * + * You have learned a way to reason about program execution: reduce expressions using + * the substitution model. + * * = Exercise = * * Complete the following method definition that computes the factorial of a number: diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala index f6df8004..c09da7e3 100644 --- a/src/main/scala/scalatutorial/sections/LexicalScopes.scala +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -136,7 +136,7 @@ object LexicalScopes extends ScalaTutorialSection { * There are two ways to overcome this problem. * * You could write the multi-line expression in parentheses, because semicolons - * are never inserted inside `(...)`: + * are never inserted inside `(…)`: * * {{{ * (someLongExpression @@ -151,20 +151,78 @@ object LexicalScopes extends ScalaTutorialSection { * someOtherExpression * }}} * - * = Summary = + * = Top-Level Definitions = * - * You have seen simple elements of functional programing in Scala. + * In real Scala programs, `def` and `val` definitions must be written + * within a top-level ''object definition'', in .scala file: * - * - arithmetic and boolean expressions - * - conditional expressions if-else - * - functions with recursion - * - nesting and lexical scope + * {{{ + * object MyExecutableProgram { + * val myVal = … + * def myMethod = … + * } + * }}} + * + * The above code defines an ''object'' named `MyExecutableProgram`. You + * can refer to its ''members'' using the usual dot notation: + * + * {{{ + * MyExecutableProgram.myMethod + * }}} + * + * The definition of `MyExecutableProgram` is ''top-level'' because it + * is not nested within another definition. * - * You have learned the difference between the call-by-name and - * call-by-value evaluation strategies. + * = Packages and Imports = + * + * Top-level definitions can be organized in ''packages'': + * + * {{{ + * // file foo/Bar.scala + * package foo + * object Bar { … } + * }}} + * + * {{{ + * // file foo/Baz.scala + * package foo + * object Baz { … } + * }}} * - * You have learned a way to reason about program execution: reduce expressions using - * the substitution model. + * Definitions located in a package are visible from other definitions + * located in the same package: + * + * {{{ + * // file foo/Baz.scala + * package foo + * object Baz { + * // Bar is visible because it is in the `foo` package too + * Bar.someMethod + * } + * }}} + * + * On the other hand, definitions located in other packages are not directly + * visible: you must use ''fully qualified names'' to refer to them: + * + * {{{ + * // file quux/Quux.scala + * package quux + * object Quux { + * foo.Bar.someMethod + * } + * }}} + * + * Finally, you can import names to avoid repeating their fully qualified form: + * + * {{{ + * // file quux/Quux.scala + * package quux + * import foo.Bar + * object Quux { + * // Bar refers to the imported `foo.Bar` + * Bar.someMethod + * } + * }}} */ def nothing(): Unit = () } From 8c2e3744f7b5ca7f44d9940f947462e84aea70cc Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 16 Nov 2016 15:02:43 +0100 Subject: [PATCH 06/19] Add TailRecursion section (end of week1) --- .../sections/TailRecursion.scala | 129 ++++++++++++++++++ .../sections/LexicalScopesSpec.scala | 15 ++ .../sections/TailRecursionSpec.scala | 15 ++ 3 files changed, 159 insertions(+) create mode 100644 src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala create mode 100644 src/test/scala/scalatutorial/sections/TailRecursionSpec.scala diff --git a/src/main/scala/scalatutorial/sections/TailRecursion.scala b/src/main/scala/scalatutorial/sections/TailRecursion.scala index 979ba811..bcb5a331 100644 --- a/src/main/scala/scalatutorial/sections/TailRecursion.scala +++ b/src/main/scala/scalatutorial/sections/TailRecursion.scala @@ -1,6 +1,135 @@ package scalatutorial.sections +import scala.annotation.tailrec + /** @param name tail_recursion */ object TailRecursion extends ScalaTutorialSection { + /** + * = Recursive Function Application = + * + * Let’s compare the evaluation steps of the application of two recursive + * methods. + * + * First, consider `gcd`, a method that computes the greatest common divisor of + * two numbers. + * + * Here's an implementation of `gcd` using Euclid's algorithm. + * + * {{{ + * def gcd(a: Int, b: Int): Int = + * if (b == 0) a else gcd(b, a % b) + * }}} + * + * `gcd(14, 21)` is evaluated as follows: + * + * {{{ + * gcd(14, 21) + * if (21 == 0) 14 else gcd(21, 14 % 21) + * if (false) 14 else gcd(21, 14 % 21) + * gcd(21, 14 % 21) + * gcd(21, 14) + * if (14 == 0) 21 else gcd(14, 21 % 14) + * gcd(14, 7) + * gcd(7, 14 % 7) + * gcd(7, 0) + * if (0 == 0) 7 else gcd(0, 7 % 0) + * 7 + * }}} + * + * Now, consider `factorial`: + * + * {{{ + * def factorial(n: Int): Int = + * if (n == 0) 1 else n * factorial(n - 1) + * }}} + * + * `factorial(4)` is evaluated as follows: + * + * {{{ + * factorial(4) + * if (4 == 0) 1 else 4 * factorial(4 - 1) + * 4 * factorial(3) + * 4 * (3 * factorial(2)) + * 4 * (3 * (2 * factorial(1))) + * 4 * (3 * (2 * (1 * factorial(0))) + * 4 * (3 * (2 * (1 * 1))) + * 24 + * }}} + * + * What are the differences between the two sequences? + * + * One important difference is that in the case of `gcd`, we see that + * the reduction sequence essentially oscillates. It goes from one call to + * `gcd` to the next one, and eventually it terminates. In between we have + * expressions that are different from a simple call like if then else's + * but we always come back to this shape of the call of `gcd`. If we look at + * `factorial`, on the other hand we see that in each couple of steps we add + * one more element to our expressions. Our expressions becomes bigger and + * bigger until we end when we finally reduce it to the final value. + * + * = Tail Recursion = + * + * That difference in the rewriting rules actually translates directly to a + * difference in the actual execution on a computer. In fact, it turns out + * that if you have a recursive function that calls itself as its last action, + * then you can reuse the stack frame of that function. This is called ''tail + * recursion''. + * + * And by applying that trick, a tail recursive function can execute in + * constant stack space, so it's really just another formulation of an + * iterative process. We could say a tail recursive function is the functional + * form of a loop, and it executes just as efficiently as a loop. + * + * If we look back at `gcd`, we see that in the else part, `gcd` calls itself + * as its last action. And that translates to a rewriting sequence that was + * essentially constant in size, and that will, in the actual execution on a + * computer, translate into a tail recursive call that can execute in constant + * space. + * + * On the other hand, if you look at `factorial` again, then you'll see that + * after the call to `factorial(n - 1)`, there is still work to be done, + * namely, we had to multiply the result of that call with the number `n`. + * So, that recursive call is not a tail recursive call, and it becomes evident in + * the reduction sequence, where you see that actually there’s a buildup of + * intermediate results that we all have to keep until we can compute the + * final value. So, `factorial` would not be a tail recursive function. + * + * Both `factorial` and `gcd` only call itself but in general, of course, a + * function could call other functions. So the generalization of tail + * recursion is that, if the last action of a function consists of calling + * another function, maybe the same, maybe some other function, the stack + * frame could be reused for both functions. Such calls are called ''tail calls''. + * + * = Tail Recursion in Scala = + * + * In Scala, only directly recursive calls to the current function are optimized. + * + * One can require that a function is tail-recursive using a `@tailrec` annotation: + * + * {{{ + * @tailrec + * def gcd(a: Int, b: Int): Int = … + * }}} + * + * If the annotation is given, and the implementation of `gcd` were not tail + * recursive, an error would be issued. + * + * = Exercise = + * + * Complete the following definition of a tail-recursive version of `factorial`: + */ + def tailRecFactorial(res0: Int, res1: Int, res2: 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) + + iter(n, res2) + } + + factorial(3) shouldBe 6 + factorial(4) shouldBe 24 + } } diff --git a/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala b/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala new file mode 100644 index 00000000..b1f1bcb6 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala @@ -0,0 +1,15 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class LexicalScopesSpec extends Spec with Checkers { + + def `check scope rules`: Unit = { + check(Test.testSuccess(LexicalScopes.scopeRules _, 16 :: HNil)) + } + +} diff --git a/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala b/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala new file mode 100644 index 00000000..d2b1c7ab --- /dev/null +++ b/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala @@ -0,0 +1,15 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class TailRecursionSpec extends Spec with Checkers { + + def `check tail recursive factorial`: Unit = { + check(Test.testSuccess(TailRecursion.tailRecFactorial _, 0 :: 1 :: 1 :: HNil)) + } + +} From 618213902c791316b6dadab91355550d40a96779 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 17 Nov 2016 13:57:16 +0100 Subject: [PATCH 07/19] Add StructuringInformation section --- .../public/scala_tutorial/music_sheet.png | Bin 0 -> 12446 bytes .../sections/StructuringInformation.scala | 220 ++++++++++++++++++ .../sections/StructuringInformationSpec.scala | 21 ++ 3 files changed, 241 insertions(+) create mode 100644 src/main/resources/public/scala_tutorial/music_sheet.png create mode 100644 src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala diff --git a/src/main/resources/public/scala_tutorial/music_sheet.png b/src/main/resources/public/scala_tutorial/music_sheet.png new file mode 100644 index 0000000000000000000000000000000000000000..63db9e039f25d066fa48931c8f5ef8f8ed509883 GIT binary patch literal 12446 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 + * + * = Aggregating Information With Case Classes = + * + * First, let’s focus on ''notes''. Suppose that, in our program, we are + * interested in the following properties of notes: their + * [[https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale name]] + * (A, B, C, etc.), their + * [[https://en.wikipedia.org/wiki/Note_value duration]] (whole, half, + * quarter, etc.) and their octave number. + * + * In summary, our note model ''aggregates'' several data (name, + * duration and octave). We express this in Scala by using a ''case class'' + * definition: + * + * {{{ + * case class Note( + * name: String, + * duration: String, + * octave: Int + * ) + * }}} + * + * This definition introduces a new type, `Note`. You can create values + * of this type by calling its ''constructor'': + * + * {{{ + * val c3 = Note("C", "Quarter", 3) + * }}} + * + * Then, you can retrieve the information carried by each ''member'' (`name`, + * `duration` and `octave`) by using the dot notation: + */ + def caseClassProjection(res0: String, res1: Int): Unit = { + case class Note(name: String, duration: String, octave: Int) + val c3 = Note("C", "Quarter", 3) + c3.name shouldBe "C" + c3.duration shouldBe res0 + c3.octave shouldBe res1 + } + + /** + * = Defining Alternatives With Sealed Traits = + * + * If we look at the introductory picture, we see that musical symbols + * can be either ''notes'' or ''rests'' (but nothing else). + * + * So, we want to introduce the concept of ''symbol'', as something + * that can be embodied by a fixed set of alternatives: a note or rest. + * We can express this in Scala using a ''sealed trait'' definition: + * + * {{{ + * sealed trait Symbol + * case class Note(name: String, duration: String, octave: Int) extends Symbol + * case class Rest(duration: String) extends Symbol + * }}} + * + * A sealed trait definition introduces a new type (here, `Symbol`), but no + * constructor for it. Constructors are defined by alternatives that + * ''extend'' the sealed trait: + * + * {{{ + * val symbol1: Symbol = Note("C", "Quarter", 3) + * val symbol2: Symbol = Rest("Whole") + * }}} + * + * = Pattern Matching = + * + * Since the `Symbol` type has no members, we can not do anything + * useful when we manipulate one. We need a way to distinguish between + * the different cases of symbols. ''Pattern matching'' allows us + * to do so: + * + * {{{ + * def symbolDuration(symbol: Symbol): String = + * symbol match { + * case Note(name, duration, octave) => duration + * case Rest(duration) => duration + * } + * }}} + * + * The above `match` expression first checks if the given `Symbol` is a + * `Note`, and if it is the case it extracts its fields (`name`, `duration` + * and `octave`) and evaluates the expression at the right of the arrow. + * Otherwise, it checks if the given `Symbol` is a `Rest`, and if it + * is the case it extracts its `duration` field, and evaluates the + * expression at the right of the arrow. + * + * When we write `case Rest(duration) => …`, we say that `Rest(…)` is a + * ''constructor pattern'': it matches all the values of type `Rest` + * that have been constructed with arguments matching the pattern `duration`. + * + * The pattern `duration` is called a ''variable pattern''. It matches + * any value and ''binds'' its name (here, `duration`) to this value. + * + * More generally, an expression of the form + * + * {{{ + * e match { + * case p1 => e1 + * case p2 => e2 + * … + * case pn => pn + * } + * }}} + * + * matches the value of the selector `e` with the patterns + * `p1`, …, `pn` in the order in which they are written. + * + * The whole match expression is rewritten to the right-hand side of the first + * case where the pattern matches the selector `e`. + * + * References to pattern variables are replaced by the corresponding + * parts in the selector. + * + * == Exhaustivity == + * + * Having defined `Symbol` as a sealed trait gives us the guarantee that + * the possible case of symbols is fixed. The compiler can leverage this + * knowledge to warn us if we write code that does not handle ''all'' + * the cases: + */ + def unexhaustive(): Unit = { + sealed trait Symbol + case class Note(name: String, duration: String, octave: Int) extends Symbol + case class Rest(duration: String) extends Symbol + + def nonExhaustiveDuration(symbol: Symbol): String = + symbol match { + case Rest(duration) => duration + } + } + + /** + * Try to run the above code to see how the compiler informs us that + * we don’t handle all the cases in `nonExhaustiveDuration`. + * + * = Equals = + * + * It is worth noting that, since the purpose of case classes is to + * aggregate values, comparing case class instances compare their values: + * + */ + def caseClassEquals(res0: Boolean, res1: Boolean): Unit = { + case class Note(name: String, duration: String, octave: Int) + val c3 = Note("C", "Quarter", 3) + val otherC3 = Note("C", "Quarter", 3) + val f3 = Note("F", "Quarter", 3) + (c3 == otherC3) shouldBe res0 + (c3 == f3) shouldBe res1 + } + + /** + * = Enumerations = + * + * Our above definition of the `Note` type allows users to create instances + * with invalid names and durations: + * + * {{{ + * val invalidNote = Note("not a name", "not a duration", 3) + * }}} + * + * It is generally a bad idea to work with a model that makes it possible + * to reach invalid states. In our case, we want to restrict the space + * of the possible note names and durations to a set of fixed alternatives. + * + * In the case of note names, the alternatives are either `A`, `B`, `C`, + * `D`, `E`, `F` or `G`. We can express the fact that note names are + * a fixed set of alternatives by using a sealed trait, but in contrast to + * the previous example alternatives are not case classes because they + * aggregate no information: + * + * {{{ + * sealed trait NoteName + * case object A extends NoteName + * case object B extends NoteName + * case object C extends NoteName + * … + * case object G extends NoteName + * }}} + * + * = Algebraic Data Types = + * + * Data types defined with sealed trait and case classes are called + * ''algebraic data types''. An algebraic data type definition can + * be thought of as a set of possible values. + * + * Algebraic data types are a powerful way to structure information. + * + * If a concept of your program’s domain can be formulated in terms of + * an ''is'' relationship, you will express it with a sealed trait: + * + * “A symbol ''is'' either a note ''or'' a rest.” + * + * {{{ + * sealed trait Symbol + * case class Note(…) extends Symbol + * case class Rest(…) extends Symbol + * }}} + * + * On the other hand, if a concept of your program’s domain can be + * formulated in terms of an ''has'' relationship, you will express it + * with a case class: + * + * “A note ''has'' a name, a duration ''and'' an octave number.” + * + * {{{ + * case class Note(name: String, duration: String, octave: Int) extends Symbol + * }}} + */ + def nothing(): Unit = () } diff --git a/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala b/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala new file mode 100644 index 00000000..4356b4af --- /dev/null +++ b/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala @@ -0,0 +1,21 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class StructuringInformationSpec extends Spec with Checkers { + + def `check case class projection`: Unit = { + check(Test.testSuccess(StructuringInformation.caseClassProjection _, "Quarter" :: 3 :: HNil)) + } + + def `check case class equals`: Unit = { + check(Test.testSuccess(StructuringInformation.caseClassEquals _, true :: false :: HNil)) + } + + + +} From c26221e9230db6d7e0f403a1043949a37fb0cb05 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 17 Nov 2016 14:46:24 +0100 Subject: [PATCH 08/19] Add HigherOrderFunctions section --- .../sections/HigherOrderFunctions.scala | 152 ++++++++++++++++++ .../sections/HigherOrderFunctionsSpec.scala | 17 ++ 2 files changed, 169 insertions(+) create mode 100644 src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala diff --git a/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala b/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala index 55f6d21d..38a97136 100644 --- a/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala +++ b/src/main/scala/scalatutorial/sections/HigherOrderFunctions.scala @@ -3,4 +3,156 @@ package scalatutorial.sections /** @param name higher_order_functions */ object HigherOrderFunctions extends ScalaTutorialSection { + /** + * = Higher-Order Functions = + * + * Functional languages treat functions as ''first-class values''. + * + * This means that, like any other value, a function + * can be passed as a parameter and returned as a result. + * + * This provides a flexible way to compose programs. + * + * Functions that take other functions as parameters or that return functions + * as results are called ''higher order functions''. + * + * = Motivation = + * + * Consider the following programs. + * + * Take the sum of the integers between `a` and `b`: + * + * {{{ + * def sumInts(a: Int, b: Int): Int = + * if (a > b) 0 else a + sumInts(a + 1, b) + * }}} + * + * Take the sum of the cubes of all the integers between `a` + * and `b`: + * + * {{{ + * def cube(x: Int): Int = x * x * x + * + * def sumCubes(a: Int, b: Int): Int = + * if (a > b) 0 else cube(a) + sumCubes(a + 1, b) + * }}} + * + * Take the sum of the factorials of all the integers between `a` + * and `b`: + * + * {{{ + * def sumFactorials(a: Int, b: Int): Int = + * if (a > b) 0 else factorial(a) + sumFactorials(a + 1, b) + * }}} + * + * Note how similar these methods are. + * Can we factor out the common pattern? + * + * = Summing with Higher-Order Functions = + * + * Let's define: + * + * {{{ + * def sum(f: Int => Int, a: Int, b: Int): Int = + * if (a > b) 0 + * else f(a) + sum(f, a + 1, b) + * }}} + * + * We can then write: + * + * {{{ + * def id(x: Int): Int = x + * def sumInts(a: Int, b: Int) = sum(id, a, b) + * def sumCubes(a: Int, b: Int) = sum(cube, a, b) + * def sumFactorials(a: Int, b: Int) = sum(factorial, a, b) + * }}} + * + * = Function Types = + * + * The type `A => B` is the type of a ''function'' that + * takes an argument of type `A` and returns a result of + * type `B`. + * + * So, `Int => Int` is the type of functions that map integers to integers. + * + * = Anonymous Functions = + * + * Passing functions as parameters leads to the creation of many small functions. + * + * Sometimes it is tedious to have to define (and name) these functions using `def`. + * + * Compare to strings: We do not need to define a string using `val`. Instead of: + * + * {{{ + * val str = "abc"; println(str) + * }}} + * + * We can directly write: + * + * {{{ + * println("abc") + * }}} + * + * because strings exist as ''literals''. Analogously we would like function + * literals, which let us write a function without giving it a name. + * + * These are called ''anonymous functions''. + * + * == Anonymous Function Syntax == + * + * Example of a function that raises its argument to a cube: + * + * {{{ + * (x: Int) => x * x * x + * }}} + * + * Here, `(x: Int)` is the ''parameter'' of the function, and + * `x * x * x` is it's ''body''. + * + * The type of the parameter can be omitted if it can be inferred by the + * compiler from the context. + * + * If there are several parameters, they are separated by commas: + * + * {{{ + * (x: Int, y: Int) => x + y + * }}} + * + * == Anonymous Functions are Syntactic Sugar == + * + * An anonymous function `(x1: T1, …, xn: Tn) => e` + * can always be expressed using `def` as follows: + * + * {{{ + * { def f(x1: T1, …, xn: Tn) = e ; f } + * }}} + * + * where `f` is an arbitrary, fresh name (that's not yet used in the program). + * + * One can therefore say that anonymous functions are ''syntactic sugar''. + * + * == Summation with Anonymous Functions == + * + * Using anonymous functions, we can write sums in a shorter way: + * + * {{{ + * def sumInts(a: Int, b: Int) = sum(x => x, a, b) + * def sumCubes(a: Int, b: Int) = sum(x => x * x * x, a, b) + * }}} + * + * = Exercise = + * + * The `sum` function uses linear recursion. Complete the following tail-recursive + * version: + */ + def tailRecSum(res0: Int, res1: Int): Unit = { + 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) + } + sum(x => x, 1, 10) shouldBe 55 + } } diff --git a/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala new file mode 100644 index 00000000..4470f99e --- /dev/null +++ b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala @@ -0,0 +1,17 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class HigherOrderFunctionsSpec extends Spec with Checkers { + + def `check tail rec sum`: Unit = { + check(Test.testSuccess(HigherOrderFunctions.tailRecSum _, 1 :: 0 :: HNil)) + } + + + +} From 1c90eb2483483956da60b709a50de0dcc0d2ac93 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 18 Nov 2016 15:57:08 +0100 Subject: [PATCH 09/19] Add StandardLibrary section --- .../sections/StandardLibrary.scala | 275 ++++++++++++++++++ .../sections/StandardLibrarySpec.scala | 15 + 2 files changed, 290 insertions(+) create mode 100644 src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala diff --git a/src/main/scala/scalatutorial/sections/StandardLibrary.scala b/src/main/scala/scalatutorial/sections/StandardLibrary.scala index 19807884..41cec0f7 100644 --- a/src/main/scala/scalatutorial/sections/StandardLibrary.scala +++ b/src/main/scala/scalatutorial/sections/StandardLibrary.scala @@ -3,4 +3,279 @@ package scalatutorial.sections /** @param name standard_library */ object StandardLibrary extends ScalaTutorialSection { + /** + * = List = + * + * The list is a fundamental data structure in functional programming. + * + * A list having `x1`, …, `xn` as elements is written `List(x1, …, xn)`: + * + * {{{ + * val fruit = List("apples", "oranges", "pears") + * val nums = List(1, 2, 3, 4) + * val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) + * val empty = List() + * }}} + * + * - Lists are immutable --- the elements of a list cannot be changed, + * - Lists are recursive (as you will see in the next subsection), + * - Lists are ''homogeneous'': the elements of a list must all have the + * same type. + * + * The type of a list with elements of type `T` is written `List[T]`: + * + * {{{ + * val fruit: List[String] = List("apples", "oranges", "pears") + * val nums : List[Int] = List(1, 2, 3, 4) + * val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1)) + * val empty: List[Nothing] = List() + * }}} + * + * == Constructors of Lists == + * + * Actually, all lists are constructed from: + * + * - the empty list `Nil`, and + * - the construction operation `::` (pronounced ''cons''): `x :: xs` gives a new list + * with the first element `x`, followed by the elements of `xs` (which is a list itself). + * + * For example: + * + * {{{ + * val fruit = "apples" :: ("oranges" :: ("pears" :: Nil)) + * val nums = 1 :: (2 :: (3 :: (4 :: Nil))) + * val empty = Nil + * }}} + * + * === Right Associativity === + * + * Convention: Operators ending in “`:`” associate to the right. + * + * `A :: B :: C` is interpreted as `A :: (B :: C)`. + * + * We can thus omit the parentheses in the definition above. + * + * {{{ + * val nums = 1 :: 2 :: 3 :: 4 :: Nil + * }}} + * + * Operators ending in “`:`” are also different in the they are seen as method calls of + * the ''right-hand'' operand. + * + * So the expression above is equivalent to: + * + * {{{ + * val nums = Nil.::(4).::(3).::(2).::(1) + * }}} + * + * == Manipulating Lists == + * + * It is possible to decompose lists with pattern matching: + * + * - `Nil`: the `Nil` constant, + * - `p :: ps`: A pattern that matches a list with a `head` matching `p` and a + * `tail` matching `ps`. + * + * {{{ + * nums match { + * // Lists of `Int` that starts with `1` and then `2` + * case 1 :: 2 :: xs => … + * // Lists of length 1 + * case x :: Nil => … + * // Same as `x :: Nil` + * case List(x) => … + * // The empty list, same as `Nil` + * case List() => + * // A list that contains as only element another list that starts with `2` + * case List(2 :: xs) => … + * } + * }}} + * + * == Exercise: Sorting Lists == + * + * Suppose we want to sort a list of numbers in ascending order: + * + * - One way to sort the list `List(7, 3, 9, 2)` is to sort the + * tail `List(3, 9, 2)` to obtain `List(2, 3, 9)`. + * - The next step is to insert the head `7` in the right place + * to obtain the result `List(2, 3, 7, 9)`. + * + * This idea describes ''Insertion Sort'': + * + * {{{ + * def isort(xs: List[Int]): List[Int] = xs match { + * case List() => List() + * case y :: ys => insert(y, isort(ys)) + * } + * }}} + * + * Complete the definition insertion sort by filling in the blanks in the definition below: + */ + def insertionSort(res0: (Int, Int) => Boolean, res1: List[Int]): Unit = { + val cond: (Int, Int) => Boolean = res0 + def insert(x: Int, xs: List[Int]): List[Int] = + xs match { + case List() => res1 + case y :: ys => + if (cond(x, y)) x :: y :: ys + else y :: insert(x, ys) + } + insert(2, 1 :: 3 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil) + insert(1, 2 :: 3 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil) + insert(3, 1 :: 2 :: Nil) shouldBe (1 :: 2 :: 3 :: Nil) + } + + /** + * == Common Operations on Lists == + * + * Transform the elements of a list using `map`: + * + * {{{ + * List(1, 2, 3).map(x => x + 1) == List(2, 3, 4) + * }}} + * + * Filter elements using `filter`: + * + * {{{ + * List(1, 2, 3).filter(x => x % 2 == 0) == List(2) + * }}} + * + * Transform each element of a list into a list and flatten the + * results into a single list using `flatMap`: + * + * {{{ + * val xs = + * List(1, 2, 3).flatMap { x => + * List(x, 2 * x, 3 * x) + * } + * xs == List(1, 2, 3, 2, 4, 6, 3, 6, 9) + * }}} + * + * = Optional Values = + * + * We represent an optional value of type `A` with the type `Option[A]`. + * This is useful to implement, for instance, partially defined + * functions: + * + * {{{ + * // The `sqrt` function is not defined for negative values + * def sqrt(x: Double): Option[Double] = … + * }}} + * + * An `Option[A]` can either be `None` (if there is no value) or `Some[A]` + * (if there is a value): + * + * {{{ + * def sqrt(x: Double): Option[Double] = + * if (x < 0) None else Some(…) + * }}} + * + * == Manipulating Options == + * + * It is possible to decompose options with pattern matching: + * + * {{{ + * def foo(x: Double): String = + * sqrt(x) match { + * case None => "no result" + * case Some(y) => y.toString + * } + * }}} + * + * == Common Operations on Options == + * + * Transform an optional value with `map`: + */ + def optionMap(res0: Option[Int], res1: Option[Int]): Unit = { + Some(1).map(x => x + 1) shouldBe Some(2) + None.map((x: Int) => x + 1) shouldBe None + res0.map(x => x + 1) shouldBe res1 + } + + /** + * Filter values with `filter`: + * + */ + def optionFilter(res0: Option[Int], res1: Option[Int]): Unit = { + Some(1).filter(x => x % 2 == 0) shouldBe None + Some(2).filter(x => x % 2 == 0) shouldBe Some(2) + res0.filter(x => x % 2 == 0) shouldBe res1 + } + + /** + * Use `flatMap` to transform a successful value into an optional value: + */ + def optionFlatMap(res0: Option[Int], res1: Option[Int]): Unit = { + Some(1).flatMap(x => Some(x + 1)) shouldBe Some(2) + None.flatMap((x: Int) => Some(x + 1)) shouldBe None + res0.flatMap(x => Some(x + 1)) shouldBe res1 + } + + /** + * = Error Handling = + * + * This subsection introduces types that are useful to handle failures. + * + * == Try == + * + * `Try[A]` represents a computation that attempted to return an `A`. It can + * either be: + * - a `Success[A]`, + * - or a `Failure`. + * + * The key difference between `None` and `Failure`s is that the latter provide + * the reason of the failure: + * + * {{{ + * def sqrt(x: Double): Try[Double] = + * if (x < 0) Failure(new IllegalArgumentException("x must be positive")) + * else Success(…) + * }}} + * + * === Manipulating `Try[A]` values === + * + * Like options and lists, `Try[A]` is an algebraic data type, so it can + * be decomposed using pattern matching. + * + * `Try[A]` also have `map`, `filter` and `flatMap`. They behave the same + * as with `Option[A]`, excepted that any exception that is thrown + * during their execution is converted into a `Failure`. + * + * == Either == + * + * `Either` can also be useful to handle failures. Basically, the type + * `Either[A, B]` represents a value that can either be of type `A` or + * of type `B`. It can be decomposed in two cases: `Left` or `Right`. + * + * You can use one case to represent the failure and the other to represent + * the success. One difference with `Try` is that you can choose another + * type than `Throwable` to represent the exception. Another difference + * is that exceptions that occur when transforming `Either` values are + * not converted into failures. + * + * {{{ + * def sqrt(x: Double): Either[String, Double] = + * if (x < 0) Left("x must be positive") + * else Right(…) + * }}} + * + * === Manipulating `Either[A, B]` Values === + * + * `Either` has `map` and `flatMap`. These methods transform the `Right` + * case only. Way say that `Either` is “right biased”: + * + * {{{ + * Right(1).map((x: Int) => x + 1) shouldBe Right(2) + * Left("foo").map((x: Int) => x + 1) shouldBe Left("foo") + * }}} + * + * `Either` also has a `filterOrElse` method that turn a `Right` value + * into a `Left` value if it does not satisfy a given predicate: + * + * {{{ + * Right(1).filterOrElse(x => x % 2 == 0, "Odd value") shouldBe Left("Odd value") + * }}} + */ + def nothing(): Unit = () + } diff --git a/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala b/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala new file mode 100644 index 00000000..44a75e1f --- /dev/null +++ b/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala @@ -0,0 +1,15 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class StandardLibrarySpec extends Spec with Checkers { + + def `check insertion sort`: Unit = { + check(Test.testSuccess(StandardLibrary.insertionSort _, ((_: Int) < (_: Int)) :: List.empty[Int] :: HNil)) + } + +} From 05a939323dfe5d3415889239e29c4288480c4eb0 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 18 Nov 2016 16:11:55 +0100 Subject: [PATCH 10/19] Add instructions --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index e48cf27b..fb6f8d81 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,45 @@ This repository hosts a library for the first course of the [Scala MOOC](https://www.coursera.org/specializations/scala) ("Functional Programming Principles in Scala"). +## Run Locally + +- Clone this repository, compile and publish the project: + +~~~ sh +git clone git@github.com:scalacenter/exercises-fpprinciples.git +cd exercises-fpprinciples/ +sbt compile publishLocal # it is important to first run the `compile` command alone +~~~ + +- Clone the `evaluator` and run it: + +~~~ sh +git clone git@github.com:scala-exercises/evaluator.git +cd evaluator/ +sbt "project evaluator-server" run +~~~ + +- Clone the `scala-tutorial` branch of our `scala-exercises` fork: + +~~~ sh +git clone -b scala-tutorial git@github.com:scalacenter/scala-exercises.git +~~~ + +- Follow the database setup instructions given + [here](https://github.com/scala-exercises/scala-exercises#configure-the-database) + +- Add the following line the `server/conf/application.dev.conf`: + +~~~ +evaluator.secretKey="secretKey" +~~~ + +- Run the server: + +~~~ sh +sbt -mem 1500 run +~~~ + ## About Scala exercises "Scala Exercises" brings exercises for the Stdlib, Cats, Shapeless and many other great libraries for Scala to your browser. Offering hundreds of solvable exercises organized into several categories covering the basics of the Scala language and it's most important libraries. From f4916917cc16bfb148da115f81ece1168f6cd8e7 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Mon, 21 Nov 2016 18:17:24 +0100 Subject: [PATCH 11/19] Add SyntacticConveniences section --- .../sections/StandardLibrary.scala | 2 +- .../sections/SyntacticConveniences.scala | 277 ++++++++++++++++++ .../sections/SyntacticConveniencesSpec.scala | 43 +++ 3 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 src/test/scala/scalatutorial/sections/SyntacticConveniencesSpec.scala diff --git a/src/main/scala/scalatutorial/sections/StandardLibrary.scala b/src/main/scala/scalatutorial/sections/StandardLibrary.scala index 41cec0f7..0b9b10c4 100644 --- a/src/main/scala/scalatutorial/sections/StandardLibrary.scala +++ b/src/main/scala/scalatutorial/sections/StandardLibrary.scala @@ -115,7 +115,7 @@ object StandardLibrary extends ScalaTutorialSection { val cond: (Int, Int) => Boolean = res0 def insert(x: Int, xs: List[Int]): List[Int] = xs match { - case List() => res1 + case List() => x :: res1 case y :: ys => if (cond(x, y)) x :: y :: ys else y :: insert(x, ys) diff --git a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala index 8f19b2ff..2d1bf7f8 100644 --- a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala +++ b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala @@ -3,4 +3,281 @@ package scalatutorial.sections /** @param name syntactic_conveniences */ object SyntacticConveniences extends ScalaTutorialSection { + /** + * This section introduces several syntactic sugars supported + * by the language. + * + * = String Interpolation = + * + * To splice values into constant `String` at runtime, you can + * use ''string interpolation'': + */ + def stringInterpolation(res0: String): Unit = { + def greet(name: String): String = + s"Hello, $name!" + + greet("Scala") shouldBe "Hello, Scala!" + greet("Functional Programming") shouldBe res0 + } + + /** + * After having prefixed the string literal with `s` you can introduce + * dynamic values in it with `$`. + * + * If you want to splice a complex expression (more than just an identifier), + * surround it with braces: + */ + def stringInterpolation2(res0: String): Unit = { + def greet(name: String): String = + s"Hello, ${name.toUpperCase}!" + + greet("Scala") shouldBe res0 + } + + /** + * = Tuples = + * + * We saw earlier that case class are useful to aggregate information. + * However, sometimes you want to aggregate information without having to define + * a complete case class for it. In such a case you can use ''tuples'': + */ + def tuples(res0: (Int, String)): Unit = { + def pair(i: Int, s: String): (Int, String) = (i, s) + + pair(42, "foo") shouldBe (42, "foo") + pair(0, "bar") shouldBe res0 + } + + /** + * In the above example, the type `(Int, String)` represents a pair whose + * first element is an `Int` and whose second element is a `String`. + * + * Similarly, the value `(i, s)` is a pair whose first element is `i` and + * whose second element is `s`. + * + * More generally, a type `(T1, …, Tn)` is a ''tuple type'' of n elements + * whose i^th^ element has type `Ti`. + * + * And a value `(t1, … tn)` is a ''tuple value'' of n elements. + * + * == Manipulating Tuples == + * + * You can retrieve the elements of a tuple by using a ''tuple pattern'': + */ + def tupleExtraction(res0: String): Unit = { + val is: (Int, String) = (42, "foo") + + is match { + case (i, s) => + i shouldBe 42 + s shouldBe res0 + } + } + + /** + * Or, simply: + */ + def tupleExtraction2(res0: String): Unit = { + val is: (Int, String) = (42, "foo") + + val (i, s) = is + i shouldBe 42 + s shouldBe res0 + } + + /** + * Alternatively, you can retrieve the 1st element with the `_1` member, + * the 2nd element with the `_2` member, etc: + */ + def tupleManipulation(res0: String): Unit = { + val is: (Int, String) = (42, "foo") + is._1 shouldBe 42 + is._2 shouldBe res0 + } + + /** + * = `for` expressions = + * + * You probably noticed that several data types of the standard library + * have methods named `map`, `flatMap` and `filter`. + * + * These methods are so common in practice that Scala supports a dedicated + * syntax: ''for expressions''. + * + * == `map` == + * + * Thus, instead of writing the following: + * + * {{{ + * xs.map(x => x + 1) + * }}} + * + * You can write: + * + * {{{ + * for (x <- xs) yield x + 1 + * }}} + * + * You can read it as “for every value, that I name ‘x’, in ‘xs’, return ‘x + 1’”. + * + * == `filter` == + * + * Also, instead of writing the following: + * + * {{{ + * xs.filter(x => x % 2 == 0) + * }}} + * + * You can write: + * + * {{{ + * for (x <- xs if x % 2 == 0) yield x + * }}} + * + * The benefit of this syntax becomes more apparent when it is combined + * with the previous one: + * + * {{{ + * for (x <- xs if x % 2 == 0) yield x + 1 + * + * // Equivalent to the following: + * xs.filter(x => x % 2 == 0).map(x => x + 1) + * }}} + * + * == `flatMap` == + * + * Finally, instead of writing the following: + * + * {{{ + * xs.flatMap(x => ys.map(y => (x, y))) + * }}} + * + * You can write: + * + * {{{ + * for (x <- xs; y <- ys) yield (x, y) + * }}} + * + * You can read it as “for every value ‘x’ in ‘xs’, and then for + * every value ‘y’ in ‘ys’, return ‘(x, y)’”. + * + * == Putting Things Together == + * + * Here is an example that puts everything together: + * + * {{{ + * for { + * x <- xs if x % 2 == 0 + * y <- ys + * } yield (x, y) + * }}} + * + * The equivalent de-sugared code is the following: + * + * {{{ + * xs.filter { x => + * x % 2 == 0 + * }.flatMap { x => + * ys.map { y => + * (x, y) + * } + * } + * }}} + * + * = Method’s Parameters = + * + * == Named Parameters == + * + * It can sometimes be difficult to figure out what is the meaning of + * each parameter passed to a function. Consider for instance the following + * expression: + * + * {{{ + * Range(1, 10, 2) + * }}} + * + * What does it mean? We can improve the readability by using ''named + * parameters''. + * + * Based on the fact that the `Range` constructor is defined as follows: + * + * {{{ + * case class Range(start: Int, end: Int, step: Int) + * }}} + * + * We can rewrite our expression as follows: + * + * {{{ + * Range(start = 1, end = 10, step = 2) + * }}} + * + * It is now clearer that this expression defines a range of numbers + * from 1 to 10 by increments of 2. + * + * == Default Values == + * + * Methods’ parameters can have default values. Let’s refine the `Range` + * constructor: + * + * {{{ + * case class Range(start: Int, end: Int, step: Int = 1) + * }}} + * + * Here, we say that the `step` parameter has a default value, `1`. + * + * Then, at use site we can omit to supply this parameter and the compiler + * will supply it for us, by using its default value: + */ + def defaultParameters(res0: Int): Unit = { + case class Range(start: Int, end: Int, step: Int = 1) + + val xs = Range(start = 1, end = 10) + + xs.step shouldBe res0 + } + + /** + * == Repeated Parameters == + * + * You can define a function that can receive an arbitrary number of + * parameters (of the same type) as follows: + */ + def repeatedParameters(res0: Double): Unit = { + def average(x: Int, xs: Int*): Double = + (x :: xs.to[List]).sum.toDouble / (xs.size + 1) + + average(1) shouldBe 1.0 + average(1, 2) shouldBe 1.5 + average(1, 2, 3) shouldBe res0 + } + + /** + * The `average` function takes at least one `Int` parameter and then + * an arbitrary number of other values and computes their average. + * By forcing users to supply at least one parameter, we make it impossible + * for them to compute the average of an empty list of numbers. + * + * Sometimes you want to supply each element of a list as as many parameters. + * You can do that by adding a `: _*` type ascription to your list: + * + * {{{ + * val xs: List[Int] = … + * average(1, xs: _*) + * }}} + * + * = Type Aliases = + * + * In the same way as you can give meaningful names to expressions, + * you can give meaningful names to ''type expressions'': + */ + def typeAlias(res0: Either[String, (Int, Int)]): Unit = { + type Result = Either[String, (Int, Int)] + def divide(dividend: Int, divisor: Int): Result = + if (divisor == 0) Left("Division by zero") + else Right((dividend / divisor, dividend % divisor)) + divide(6, 4) shouldBe Right((1, 2)) + divide(2, 0) shouldBe Left("Division by zero") + divide(8, 4) shouldBe res0 + } + } diff --git a/src/test/scala/scalatutorial/sections/SyntacticConveniencesSpec.scala b/src/test/scala/scalatutorial/sections/SyntacticConveniencesSpec.scala new file mode 100644 index 00000000..82f0c16a --- /dev/null +++ b/src/test/scala/scalatutorial/sections/SyntacticConveniencesSpec.scala @@ -0,0 +1,43 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class SyntacticConveniencesSpec extends Spec with Checkers { + + def `check string interpolation`: Unit = { + check(Test.testSuccess(SyntacticConveniences.stringInterpolation _, "Hello, Functional Programming!" :: HNil)) + } + + def `check string interpolation2`: Unit = { + check(Test.testSuccess(SyntacticConveniences.stringInterpolation2 _, "Hello, SCALA!" :: HNil)) + } + + def `check tuples`: Unit = { + check(Test.testSuccess(SyntacticConveniences.tupleExtraction _, "foo" :: HNil)) + } + + def `check tuples2`: Unit = { + check(Test.testSuccess(SyntacticConveniences.tupleExtraction2 _, "foo" :: HNil)) + } + + def `check tuples manipulation`: Unit = { + check(Test.testSuccess(SyntacticConveniences.tupleManipulation _, "foo" :: HNil)) + } + + def `check default parameters`: Unit = { + check(Test.testSuccess(SyntacticConveniences.defaultParameters _, 1 :: HNil)) + } + + def `check repeated parameters`: Unit = { + check(Test.testSuccess(SyntacticConveniences.repeatedParameters _, 2.0 :: HNil)) + } + + def `check type alias`: Unit = { + check(Test.testSuccess(SyntacticConveniences.typeAlias _, (Right((2, 0)): Either[String, (Int, Int)]) :: HNil)) + } + +} From 543fa5c1da5ade1553d813f1b04b67ed9923d869 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 23 Nov 2016 16:53:43 +0100 Subject: [PATCH 12/19] Add ObjectOrientedPrograming section --- .../scala_tutorial/scala_type_hierarchy.png | Bin 0 -> 122175 bytes src/main/scala/scalatutorial/aux/IntSet.scala | 23 + .../sections/LexicalScopes.scala | 46 +- .../sections/ObjectOrientedProgramming.scala | 642 +++++++++++++++++- .../sections/StructuringInformation.scala | 2 +- .../sections/SyntacticConveniences.scala | 2 +- .../sections/TermsAndTypes.scala | 2 + .../sections/HigherOrderFunctionsSpec.scala | 2 - .../ObjectOrientedProgrammingSpec.scala | 17 + 9 files changed, 730 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/public/scala_tutorial/scala_type_hierarchy.png create mode 100644 src/main/scala/scalatutorial/aux/IntSet.scala create mode 100644 src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala diff --git a/src/main/resources/public/scala_tutorial/scala_type_hierarchy.png b/src/main/resources/public/scala_tutorial/scala_type_hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..e18279eb32562a935b7d4eea60b3d8a212306da4 GIT binary patch literal 122175 zcmb?@c|4cx_Vu@tR5GNbB#KfQL&;1?BvWOI3<-(IEVGnK5|TNg44IN7vk1wYd7cTG z$;`XHo^#IceBR-|_de%&PKWs3_jO%+@3q!m`+lmVAhT!Jp9kYdH{3=#r8nX{QnmGDJRdd5 z$gwAHC!&v?d}7R=Chb!iZp@b^C@$xUHeCvj=kL{@r;Z;&n#rp zSHOre^3V--w}nZ;=B(gWJ42($&aWMnm9x)YvdH2Fh*$1izE}VJi9mIJ%Rhf|$88d? z|9s&;fB66UowqtPk~;~6p0l0#&O)b7?X+3sPD)N@y%OpvNFWrtow(r7@&R{|T3l6? zoSUnqs7U$VzWa!n3p+D&Kw#h_>_eg3W8QEAVJe$s>o=oF=f9nH-BJ4{+o$`>6eC4_ zh`&YL%klsDRR4Y$!oUCUe>~>@zwhY2&9>m&i6BBb)MdjGBV@i?)H_AWd0jxegJ`FWCHa2I@oM~)qY|y`b z`?kD%Wqq7tLWvh8V`*vWIez}D-V2l6zTVz<&CTOzXJ=;m`uiOpJcuuO{`~p(?^2?X ziX5@H-n~;2+fCwuuk4&zdwHN+~02xcQoir zN=o8$Vq|>dOF~0KgM)+H+B8k3>tbYZBMlX@ii(Qz^83h08YEAhI+c)+&`{$fDu!<{ zGe6Y)GcqE3>C%siiZMsRqeqV#^sTI{Oilk3dr>Bo#3@FWe*b<=RaM0skAok;qc!L| zI5^15%U``plcBDnvX`E|y{%1-gLdD(?`36nHa5!Mt*xy)cka~U4APXpeEG7R+%Z{y zCNh7PfPet=?))nv-}|Tc($Mho@^;UNoWN1OiM6Pxjt~-?3fM*B@$A{N(9lEf;!`tz zVrz2&FJ4rKO}KvZ*gi2aaqir?bi=AqvG=03e}c2J1Vk+f++WxJwwq%I^7He<4JqP6 z((kPa3P$8Rl~|gb(Ul-XD0JsK@MfGhYvU7)aOvsUVp3OH`dA@u2Pvs#sKAx*6Wg|J z6O5?VPd{H3-j=P&!NDOI@#X-)RepbCx8D>D$4h%<3a-7_$kEIH=b>{YjPKV z_emJFwV<|+PC;6NUw!3H3X0hJk=L(ZH>4jHu=G@jQ@kH}f#Y6hcA!GsO4V!(ZNXyS zZo!E4l2NXBX&oIM+@RUYm(aFDHhXOFd2jrx?+oqRe?WQ1R8Q|V1zy&9)zc9Q&A)zq zNKPE&vr;#ujZom{<~Hu*yB{jxF5zx1*olW|8+L!VU*j2p|7pU49BiQ8D-*l6?Xoou)BLZ?u-)!n;e&-Sxjkd$0#OmRf`cWrD)wTGh;Ys+A=d(!Ee?kMfE-3dF91n zftkhWTOLfDI{CS`%3k#K=^s3Jkd-wqfK_qy_iy2iTr)E>O-)T!#W$wL#*0H?<|Za4 z*REZQFNnEu<3^;2qwVZa72>VAp+Oj@29f=E5B=V~dp~{p6kpZf*O!}@hm$xzKi_R8 z@q$szWqEESJp8cJC_z{0nCz$I;kD7W3 zcSz=O-f7W7N=nMq)KpTkxGT?*-@Ge4BBImIe9xXe>+9>gY1!iw63QzplTuSRa$Y(O z{@j1a=iJ?<5M!T(g zO1uy3At�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 T872Qk{Qc>s=2Narvi1KTnCsJ% literal 0 HcmV?d00001 diff --git a/src/main/scala/scalatutorial/aux/IntSet.scala b/src/main/scala/scalatutorial/aux/IntSet.scala new file mode 100644 index 00000000..34dd77d2 --- /dev/null +++ b/src/main/scala/scalatutorial/aux/IntSet.scala @@ -0,0 +1,23 @@ +package scalatutorial.aux + +abstract class IntSet { + def incl(x: Int): IntSet + def contains(x: Int): Boolean +} + +object Empty extends IntSet { + def contains(x: Int): Boolean = false + def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty) +} +class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet { + + def contains(x: Int): Boolean = + if (x < elem) left contains x + else if (x > elem) right contains x + else true + + def incl(x: Int): IntSet = + if (x < elem) new NonEmpty(elem, left incl x, right) + else if (x > elem) new NonEmpty(elem, left, right incl x) + else this +} \ No newline at end of file diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala index c09da7e3..710221a6 100644 --- a/src/main/scala/scalatutorial/sections/LexicalScopes.scala +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -175,7 +175,9 @@ object LexicalScopes extends ScalaTutorialSection { * * = Packages and Imports = * - * Top-level definitions can be organized in ''packages'': + * Top-level definitions can be organized in ''packages''. + * To place a class or object inside a package, use a package clause + * at the top of your source file: * * {{{ * // file foo/Bar.scala @@ -223,6 +225,48 @@ object LexicalScopes extends ScalaTutorialSection { * Bar.someMethod * } * }}} + * + * = Automatic Imports = + * + * Some entities are automatically imported in any Scala program. + * + * These are: + * + * - All members of package `scala` + * - All members of package `java.lang` + * - All members of the singleton object `scala.Predef`. + * + * Here are the fully qualified names of some types and functions + * which you have seen so far: + * + * {{{ + * Int scala.Int + * Boolean scala.Boolean + * Object java.lang.Object + * String java.lang.String + * }}} + * + * = Writing Executable Programs = + * + * So far our examples of code were executed from your Web + * browser, but it is also possible to create standalone + * applications in Scala. + * + * Each such application contains an object with a `main` method. + * + * For instance, here is the "Hello World!" program in Scala: + * + * {{{ + * object Hello { + * def main(args: Array[String]) = println("hello world!") + * } + * }}} + * + * Once this program is compiled, you can start it from the command line with + * + * {{{ + * $ scala Hello + * }}} */ def nothing(): Unit = () } diff --git a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala index 6f94a840..28a44639 100644 --- a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala +++ b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala @@ -1,6 +1,646 @@ package scalatutorial.sections +import scalatutorial.aux.{IntSet, NonEmpty, Empty} + /** @param name object_oriented_programming */ object ObjectOrientedProgramming extends ScalaTutorialSection { -} + /** + * = Functions and Data = + * + * Let’s see how functions create and encapsulate data + * structures. + * + * We want to design a package for doing rational arithmetic. + * + * A rational number `x / y` is represented by two integers: + * + * - its ''numerator'' `x`, and + * - its ''denominator'' `y`. + * + * = Rational Addition = + * + * Suppose we want to implement the addition of two rational numbers. + * + * {{{ + * def addRationalNumerator(n1: Int, d1: Int, n2: Int, d2: Int): Int + * def addRationalDenominator(n1: Int, d1: Int, n2: Int, d2: Int): Int + * }}} + * + * It would be difficult to manage all these numerators and denominators! + * + * A better choice is to combine the numerator and + * denominator of a rational number in a data structure. + * + * = Classes = + * + * In Scala, we do this by defining a ''class'': + * + * {{{ + * class Rational(x: Int, y: Int) { + * def numer = x + * def denom = y + * } + * }}} + * + * This definition introduces two entities: + * + * - A new ''type'', named `Rational`. + * - A ''constructor'' `Rational` to create elements of this type. + * + * Scala keeps the names of types and values in ''different namespaces''. + * So there's no conflict between the two definitions of `Rational`. + * + * = Objects = + * + * We call the elements of a class type ''objects''. + * + * We create an object by prefixing an application of the constructor of + * the class with the operator `new`. + * + * {{{ + * new Rational(1, 2) + * }}} + * + * = Members of an Object = + * + * Objects of the class `Rational` have two ''members'', + * `numer` and `denom`. + * + * We select the members of an object with the infix operator `.` (like in Java). + * + * {{{ + * val x = new Rational(1, 2) // x: Rational = Rational@2abe0e27 + * x.numer // 1 + * x.denom // 2 + * }}} + * + * = Rational Arithmetic = + * + * We can now define the arithmetic functions that implement the standard rules. + * + * {{{ + * n1 / d1 + n2 / d2 = (n1 * d2 + n2 * d1) / (d1 * d2) + * n1 / d1 - n2 / d2 = (n1 * d2 - n2 * d1) / (d1 * d2) + * n1 / d1 * n2 / d2 = (n1 * n2) / (d1 * d2) + * n1 / d1 / n2 / d2 = (n1 * d2) / (d1 * n2) + * n1 / d1 = n2 / d2 iff n1 * d2 = d1 * n2 + * }}} + * + * = Implementing Rational Arithmetic = + * + * {{{ + * def addRational(r: Rational, s: Rational): Rational = + * new Rational( + * r.numer * s.denom + s.numer * r.denom, + * r.denom * s.denom + * ) + * + * def makeString(r: Rational) = + * r.numer + "/" + r.denom + * }}} + * + * And then: + * + * {{{ + * makeString(addRational(new Rational(1, 2), new Rational(2, 3))) + * }}} + * + * = Methods = + * + * One can go further and also package functions operating on a data + * abstraction in the data abstraction itself. + * + * Such functions are called ''methods''. + * + * Rational numbers now would have, in addition to the functions `numer` + * and `denom`, the functions `add`, `sub`, + * `mul`, `div`, `equal`, `toString`. + * + * Here's a possible implementation: + * + * {{{ + * class Rational(x: Int, y: Int) { + * def numer = x + * def denom = y + * def add(r: Rational) = + * new Rational(numer * r.denom + r.numer * denom, denom * r.denom) + * def mul(r: Rational) = ... + * ... + * override def toString = numer + "/" + denom + * } + * }}} + * + * Note that the modifier `override` declares that `toString` + * redefines a method that already exists (in the class `java.lang.Object`). + * + * Here is how one might use the new `Rational` abstraction: + * + * {{{ + * val x = new Rational(1, 3) + * val y = new Rational(5, 7) + * val z = new Rational(3, 2) + * x.add(y).mul(z) + * }}} + * + * = Data Abstraction = + * + * In the above example rational numbers weren't always + * represented in their simplest form. + * + * One would expect the rational numbers to be ''simplified'': + * + * - reduce them to their smallest numerator and denominator by dividing both with a divisor. + * + * We could implement this in each rational operation, but it would be easy + * to forget this division in an operation. + * + * A better alternative consists of simplifying the representation in + * the class when the objects are constructed: + * + * {{{ + * class Rational(x: Int, y: Int) { + * private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + * private val g = gcd(x, y) + * def numer = x / g + * def denom = y / g + * ... + * } + * }}} + * + * `gcd` and `g` are ''private'' members; we can only access them + * from inside the `Rational` class. + * + * In this example, we calculate `gcd` immediately, so that its value can be re-used + * in the calculations of `numer` and `denom`. + * + * It is also possible to call `gcd` in the code of + * `numer` and `denom`: + * + * {{{ + * class Rational(x: Int, y: Int) { + * private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + * def numer = x / gcd(x, y) + * def denom = y / gcd(x, y) + * } + * }}} + * + * This can be advantageous if it is expected that the functions `numer` + * and `denom` are called infrequently. + * + * It is equally possible to turn `numer` and `denom` into `val`s, so that they are computed only once: + * + * {{{ + * class Rational(x: Int, y: Int) { + * private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + * val numer = x / gcd(x, y) + * val denom = y / gcd(x, y) + * } + * }}} + * + * This can be advantageous if the functions `numer` and `denom` are called often. + * + * == The Client's View == + * + * Clients observe exactly the same behavior in each case. + * + * This ability to choose different implementations of the data without affecting + * clients is called ''data abstraction''. + * + * It is a cornerstone of software engineering. + * + * = Self Reference = + * + * On the inside of a class, the name `this` represents the object + * on which the current method is executed. + * + * Add the functions `less` and `max` to the class `Rational`. + * + * {{{ + * class Rational(x: Int, y: Int) { + * ... + * def less(that: Rational) = + * numer * that.denom < that.numer * denom + * + * def max(that: Rational) = + * if (this.less(that)) that else this + * } + * }}} + * + * Note that a simple name `x`, which refers to another member + * of the class, is an abbreviation of `this.x`. Thus, an equivalent + * way to formulate `less` is as follows. + * + * {{{ + * def less(that: Rational) = + * this.numer * that.denom < that.numer * this.denom + * }}} + * + * = Preconditions = + * + * Let's say our `Rational` class requires that the denominator is positive. + * + * We can enforce this by calling the `require` function. + * + * {{{ + * class Rational(x: Int, y: Int) { + * require(y > 0, "denominator must be positive") + * ... + * } + * }}} + * + * `require` is a predefined function. It takes a condition and an optional message string. + * If the condition passed to `require` is `false`, an `IllegalArgumentException` is thrown + * with the given message string. + * + * = Assertions = + * + * Besides `require`, there is also `assert`. + * + * Assert also takes a condition and an optional message string as parameters. E.g. + * + * {{{ + * val x = sqrt(y) + * assert(x >= 0) + * }}} + * + * Like `require`, a failing `assert` will also throw an exception, but it's a + * different one: `AssertionError` for `assert`, `IllegalArgumentException` for `require`. + * + * This reflects a difference in intent + * + * - `require` is used to enforce a precondition on the caller of a function. + * - `assert` is used as to check the code of the function itself. + * + * = Constructors = + * + * In Scala, a class implicitly introduces a constructor. This one + * is called the ''primary constructor'' of the class. + * + * The primary constructor: + * + * - takes the parameters of the class + * - and executes all statements in the class body + * (such as the `require` a couple of slides back). + * + * == Auxiliary Constructors == + * + * Scala also allows the declaration of ''auxiliary constructors''. + * + * These are methods named `this`. + * + * Adding an auxiliary constructor to the class `Rational`: + * + * {{{ + * class Rational(x: Int, y: Int) { + * def this(x: Int) = this(x, 1) + * ... + * } + * }}} + * + * = Classes and Substitutions = + * + * We previously defined the meaning of a function application using + * a computation model based on substitution. Now we extend this + * model to classes and objects. + * + * How is an instantiation of the class `new C(e1, …, en)` evaluted? + * + * The expression arguments `e1, …, en` + * are evaluated like the arguments of a normal function. That's it. + * + * The resulting expression, say, `new C(v1, …, vn)`, is + * already a value. + * + * Now suppose that we have a class definition, + * + * {{{ + * class C(x1, …, xn) { + * … + * def f(y1, …, ym) = b + * … + * } + * }}} + * + * where: + * + * - The formal parameters of the class are `x1, …, xn`. + * - The class defines a method `f` with formal parameters + * `y1, …, ym`. + * + * (The list of function parameters can be absent. For simplicity, we + * have omitted the parameter types.) + * + * How is the following expression evaluated? + * + * {{{ + * new C(v1, …, vn).f(w1, …, wm) + * }}} + * + * The following three substitutions happen: + * + * - the substitution of the formal parameters `y1, …, ym` of the function `f` by the + * arguments `w1, …, wm`, + * - the substitution of the formal parameters `x1, …, xn` of the class `C` by the class + * arguments `v1, …, vn`, + * - the substitution of the self reference `this` by the value of the + * object `new C(v1, …, vn)`. + * + * = Operators = + * + * In principle, the rational numbers defined by `Rational` are + * as natural as integers. + * + * But for the user of these abstractions, there is a noticeable + * difference: + * + * - We write `x + y`, if `x` and `y` are integers, but + * - We write `r.add(s)` if `r` and `s` are rational numbers. + * + * In Scala, we can eliminate this difference because operators can be used as identifiers. + * + * Thus, an identifier can be: + * + * - ''Alphanumeric'': starting with a letter, followed by a sequence of letters or numbers + * - ''Symbolic'': starting with an operator symbol, followed by other operator symbols. + * - The underscore character `'_'` counts as a letter. + * - Alphanumeric identifiers can also end in an underscore, followed by some operator symbols. + * + * Examples of identifiers: + * + * {{{ + * x1 * +?%& vector_++ counter_= + * }}} + * + * == Operators for Rationals == + * + * So, here is a more natural definition of class `Rational`: + * + * {{{ + * class Rational(x: Int, y: Int) { + * private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + * private val g = gcd(x, y) + * def numer = x / g + * def denom = y / g + * def + (r: Rational) = + * new Rational( + * numer * r.denom + r.numer * denom, + * denom * r.denom + * ) + * def - (r: Rational) = ... + * def * (r: Rational) = ... + * ... + * } + * }}} + * + * and then rational numbers can be used like `Int` or `Double`: + * + * {{{ + * val x = new Rational(1, 2) + * val y = new Rational(1, 3) + * x * x + y * y + * }}} + * + * = Precedence Rules = + * + * The ''precedence'' of an operator is determined by its first character. + * + * The following table lists the characters in increasing order of priority precedence: + * + * {{{ + * (all letters) + * | + * ^ + * & + * < > + * = ! + * : + * + - + * * / % + * (all other special characters) + * }}} + * + * = Abstract Classes = + * + * Consider the task of writing a class for sets of integers with + * the following operations. + * + * {{{ + * abstract class IntSet { + * def incl(x: Int): IntSet + * def contains(x: Int): Boolean + * } + * }}} + * + * `IntSet` is an ''abstract class''. + * + * Abstract classes can contain members which are + * missing an implementation (in our case, `incl` and `contains`). + * + * Consequently, no instances of an abstract class can be created with + * the operator `new`. + * + * = Class Extensions = + * + * Let's consider implementing sets as binary trees. + * + * There are two types of possible trees: a tree for the empty set, and + * a tree consisting of an integer and two sub-trees. + * + * Here are their implementations: + * + * {{{ + * class Empty extends IntSet { + * def contains(x: Int): Boolean = false + * def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty) + * } + * + * class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet { + * + * def contains(x: Int): Boolean = + * if (x < elem) left contains x + * else if (x > elem) right contains x + * else true + * + * def incl(x: Int): IntSet = + * if (x < elem) new NonEmpty(elem, left incl x, right) + * else if (x > elem) new NonEmpty(elem, left, right incl x) + * else this + * } + * }}} + * + * `Empty` and `NonEmpty` both ''extend'' the class `IntSet`. + * + * This implies that the types `Empty` and `NonEmpty` ''conform'' to the type `IntSet` + * + * - an object of type `Empty` or `NonEmpty` can be used wherever an object of type + * `IntSet` is required. + * + * `IntSet` is called the ''superclass'' of `Empty` + * and `NonEmpty`. + * + * `Empty` and `NonEmpty` are ''subclasses'' of + * `IntSet`. + * + * In Scala, any user-defined class extends another class. + * + * If no superclass is given, the standard class `Object` in the Java package `java.lang` is assumed. + * + * The direct or indirect superclasses of a class `C` are called ''base classes'' of `C`. + * + * So, the base classes of `NonEmpty` are `IntSet` and `Object`. + * + * = Implementation and Overriding = + * + * The definitions of `contains` and `incl` in the classes + * `Empty` and `NonEmpty` ''implement'' the abstract + * functions in the base trait `IntSet`. + * + * It is also possible to ''redefine'' an existing, non-abstract + * definition in a subclass by using `override`. + * + * {{{ + * abstract class Base { + * def foo = 1 + * def bar: Int + * } + * + * class Sub extends Base { + * override def foo = 2 + * def bar = 3 + * } + * }}} + * + * = Object Definitions = + * + * In the `IntSet` example, one could argue that there is really only a + * single empty `IntSet`. + * + * So it seems overkill to have the user create many instances of it. + * + * We can express this case better with an ''object definition'': + * + * {{{ + * object Empty extends IntSet { + * def contains(x: Int): Boolean = false + * def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty) + * } + * }}} + * + * This defines a ''singleton object'' named `Empty`. + * + * No other `Empty` instances can be (or need to be) created. + * + * Singleton objects are values, so `Empty` evaluates to itself. + * + +Exercise +======== + +Write a method `union` for forming the union of two sets. You should +implement the following abstract class. + + abstract class IntSet { + def incl(x: Int): IntSet + def contains(x: Int): Boolean + def union(other: IntSet): IntSet + } + + * + * = Dynamic Binding = + * + * Object-oriented languages (including Scala) implement + * ''dynamic method dispatch''. + * + * This means that the code invoked by a method call depends on the + * runtime type of the object that contains the method. + */ + def dynamicBinding(res0: Boolean, res1: Boolean): Unit = { + Empty contains 1 shouldBe res0 + new NonEmpty(7, Empty, Empty) contains 7 shouldBe res1 + } + +/** + * Dynamic dispatch of methods is analogous to calls to + * higher-order functions. + * + * Can we implement one concept in terms of the other? + * + * - Objects in terms of higher-order functions? + * - Higher-order functions in terms of objects? + * + * = Traits = + * + * In Java, as well as in Scala, a class can only have one superclass. + * + * But what if a class has several natural supertypes to which it conforms + * or from which it wants to inherit code? + * + * Here, you could use `trait`s. + * + * A trait is declared like an abstract class, just with `trait` instead of + * `abstract class`. + * + * {{{ + * trait Planar { + * def height: Int + * def width: Int + * def surface = height * width + * } + * }}} + * + * Classes, objects and traits can inherit from at most one class but + * arbitrary many traits: + * + * {{{ + * class Square extends Shape with Planar with Movable … + * }}} + * + * Traits cannot have (value) parameters, only classes can. + * + * = Scala's Class Hierarchy = + * + * + * + * == Top Types == + * + * At the top of the type hierarchy we find: + * + * - `Any` + * - The base type of all types + * - Methods: `==`, `!=`, `equals`, `hashCode`, `toString` + * - `AnyRef` + * - The base type of all reference types + * - Alias of `java.lang.Object` + * - `AnyVal` + * - The base type of all primitive types + * + * == Bottom Type == + * + * `Nothing` is at the bottom of Scala's type hierarchy. It is a subtype + * of every other type. + * + * There is no value of type `Nothing`. + * + * Why is that useful? + * + * - To signal abnormal termination + * - As an element type of empty collections + * + * == The Null Type == + * + * Every reference class type also has `null` as a value. + * + * The type of `null` is `Null`. + * + * `Null` is a subtype of every class that inherits from `Object`; it is + * incompatible with subtypes of `AnyVal`. + * + * {{{ + * val x = null // x: Null + * val y: String = null // y: String + * val z: Int = null // error: type mismatch + * }}} + */ + def nothing(): Unit = () +} \ No newline at end of file diff --git a/src/main/scala/scalatutorial/sections/StructuringInformation.scala b/src/main/scala/scalatutorial/sections/StructuringInformation.scala index 4eb48c6b..d8c31924 100644 --- a/src/main/scala/scalatutorial/sections/StructuringInformation.scala +++ b/src/main/scala/scalatutorial/sections/StructuringInformation.scala @@ -12,7 +12,7 @@ object StructuringInformation extends ScalaTutorialSection { * This section introduces the ways you can structure information in Scala. * We will base our examples on the following domain, a ''music sheet'': * - * + * * * = Aggregating Information With Case Classes = * diff --git a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala index 2d1bf7f8..c8ef2bd4 100644 --- a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala +++ b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala @@ -37,7 +37,7 @@ object SyntacticConveniences extends ScalaTutorialSection { /** * = Tuples = * - * We saw earlier that case class are useful to aggregate information. + * We saw earlier that case classes are useful to aggregate information. * However, sometimes you want to aggregate information without having to define * a complete case class for it. In such a case you can use ''tuples'': */ diff --git a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala index 391a20ba..4751feb2 100644 --- a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala +++ b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala @@ -138,6 +138,8 @@ object TermsAndTypes extends ScalaTutorialSection { * 1.to(10) == 1 to 10 * }}} * + * Any method with a parameter can be used like an infix operator. + * * = Values and Types = * * Expressions have a ''value'' and a ''type''. The evaluation model diff --git a/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala index 4470f99e..ff3f0948 100644 --- a/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala +++ b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala @@ -12,6 +12,4 @@ class HigherOrderFunctionsSpec extends Spec with Checkers { check(Test.testSuccess(HigherOrderFunctions.tailRecSum _, 1 :: 0 :: HNil)) } - - } diff --git a/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala b/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala new file mode 100644 index 00000000..5067eb86 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala @@ -0,0 +1,17 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class ObjectOrientedProgrammingSpec extends Spec with Checkers { + + def `check dynamic binding`: Unit = { + check(Test.testSuccess(ObjectOrientedProgramming.dynamicBinding _, false :: true :: HNil)) + } + + + +} From f25cf97a94c3f5f3a583f3902d2c247367b6ad26 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 23 Nov 2016 17:58:34 +0100 Subject: [PATCH 13/19] Add ImperativeProgramming section --- .../scala/scalatutorial/aux/BankAccount.scala | 16 + .../sections/ImperativeProgramming.scala | 327 ++++++++++++++++++ .../sections/ObjectOrientedProgramming.scala | 18 +- .../sections/ImperativeProgrammingSpec.scala | 15 + 4 files changed, 360 insertions(+), 16 deletions(-) create mode 100644 src/main/scala/scalatutorial/aux/BankAccount.scala create mode 100644 src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala diff --git a/src/main/scala/scalatutorial/aux/BankAccount.scala b/src/main/scala/scalatutorial/aux/BankAccount.scala new file mode 100644 index 00000000..8f84021f --- /dev/null +++ b/src/main/scala/scalatutorial/aux/BankAccount.scala @@ -0,0 +1,16 @@ +package scalatutorial.aux + +class BankAccount { + + private var balance = 0 + + def deposit(amount: Int): Unit = { + if (amount > 0) balance = balance + amount + } + + def withdraw(amount: Int): Int = + if (0 < amount && amount <= balance) { + balance = balance - amount + balance + } else throw new Error("insufficient funds") +} \ No newline at end of file diff --git a/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala index 1c88515b..b839fb20 100644 --- a/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala +++ b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala @@ -1,6 +1,333 @@ package scalatutorial.sections +import scalatutorial.aux.BankAccount + /** @param name imperative_programming */ object ImperativeProgramming extends ScalaTutorialSection { + /** + * Until now, our programs have been side-effect free. + * + * Therefore, the concept of ''time'' wasn't important. + * + * For all programs that terminate, any sequence of actions would have given the same results. + * + * This was also reflected in the substitution model of computation. + * + * = Reminder: Substitution Model = + * + * Programs can be evaluated by ''rewriting'': + * - a name is evaluated by replacing it with the right-hand side of its definition, + * - function application is evaluated by replacing it with the function’s right-hand + * side, and, at the same time, by replacing the formal parameters by the actual + * arguments. + * + * Say you have the following two functions `iterate` and `square`: + * + * {{{ + * def iterate(n: Int, f: Int => Int, x: Int) = + * if (n == 0) x else iterate(n-1, f, f(x)) + * def square(x: Int) = x * x + * }}} + * + * Then the call `iterate(1, square, 3)` gets rewritten as follows: + * + * {{{ + * iterate(1, square, 3) + * if (1 == 0) 3 else iterate(1-1, square, square(3)) + * iterate(0, square, square(3)) + * iterate(0, square, 3 * 3) + * iterate(0, square, 9) + * if (0 == 0) 9 else iterate(0-1, square, square(9)) + * 9 + * }}} + * + * Rewriting can be done anywhere in a term, and all rewritings which + * terminate lead to the same solution. + * + * This is an important result of the λ-calculus, the theory + * behind functional programming. + * + * For instance, these two rewriting will eventually lead to the same result: + * + * {{{ + * if (1 == 0) 3 else iterate(1 - 1, square, square(3)) + * iterate(0, square, square(3)) + * + * // OR + * if (1 == 0) 3 else iterate(1 - 1, square, square(3)) + * if (1 == 0) 3 else iterate(1 - 1, square, 3 * 3) + * }}} + * + * = Stateful Objects = + * + * One normally describes the world as a set of objects, some of which + * have state that ''changes'' over the course of time. + * + * An object ''has a state'' if its behavior is influenced by its + * history. + * + * Example: a bank account has a state, because the answer to the question + * “can I withdraw 100 CHF ?” may vary over the course of the lifetime of + * the account. + * + * = Implementation of State = + * + * Every form of mutable state is constructed from variables. + * + * A variable definition is written like a value definition, but with the + * keyword `var` in place of `val`: + * + * {{{ + * var x: String = "abc" + * var count = 111 + * }}} + * + * Just like a value definition, a variable definition associates a value + * with a name. + * + * However, in the case of variable definitions, this association can be + * changed later through an ''assignment'': + * + * {{{ + * x = "hi" + * count = count + 1 + * }}} + * + * = State in Objects = + * + * In practice, objects with state are usually represented by objects that + * have some variable members. + * + * Here is a class modeling a bank account: + * + * {{{ + * class BankAccount { + * private var balance = 0 + * def deposit(amount: Int): Int = { + * if (amount > 0) balance = balance + amount + * balance + * } + * def withdraw(amount: Int): Int = + * if (0 < amount && amount <= balance) { + * balance = balance - amount + * balance + * } else throw new Error("insufficient funds") + * } + * }}} + * + * The class `BankAccount` defines a variable `balance` that contains the + * current balance of the account. + * + * The methods `deposit` and `withdraw` change the value of the `balance` + * through assignments. + * + * Note that `balance` is `private` in the `BankAccount` + * class, it therefore cannot be accessed from outside the class. + * + * To create bank accounts, we use the usual notation for object creation: + * + * {{{ + * val account = new BankAccount + * }}} + * + * = Working with Mutable Objects = + * + * Here is a program that manipulates bank accounts. + * + * {{{ + * val account = new BankAccount // account: BankAccount = BankAccount + * account deposit 50 // + * account withdraw 20 // res1: Int = 30 + * account withdraw 20 // res2: Int = 10 + * account withdraw 15 // java.lang.Error: insufficient funds + * }}} + * + * Applying the same operation to an account twice in a row produces different results. + * Clearly, accounts are stateful objects. + * + * = Identity and Change = + * + * Assignment poses the new problem of deciding whether two expressions + * are "the same" + * + * When one excludes assignments and one writes: + * + * {{{ + * val x = E; val y = E + * }}} + * + * where `E` is an arbitrary expression, then it is reasonable to assume that + * `x` and `y` are the same. That is to say that we could have also written: + * + * {{{ + * val x = E; val y = x + * }}} + * + * (This property is usually called ''referential transparency'') + * + * But once we allow the assignment, the two formulations are different. For example: + * + * {{{ + * val x = new BankAccount + * val y = new BankAccount + * }}} + * + * Are `x` and `y` the same? + * + * = Operational Equivalence = + * + * To respond to the last question, we must specify what is meant by “the same”. + * + * The precise meaning of “being the same” is defined by the property of + * ''operational equivalence''. + * + * In a somewhat informal way, this property is stated as follows: + * + * - Suppose we have two definitions `x` and `y`. + * - `x` and `y` are operationally equivalent if ''no possible test'' can + * distinguish between them. + * + * = Testing for Operational Equivalence = + * + * To test if `x` and `y` are the same, we must + * + * - Execute the definitions followed by an arbitrary sequence `S` of operations that + * involves `x` and `y`, observing the possible outcomes. + * + * {{{ + * val x = new BankAccount + * val y = new BankAccount + * f(x, y) + * }}} + * + * - Then, execute the definitions with another sequence `S'` obtained by + * renaming all occurrences of `y` by `x` in `S`: + * + * {{{ + * val x = new BankAccount + * val y = new BankAccount + * f(x, x) + * }}} + * + * - If the results are different, then the expressions `x` and `y` are certainly different. + * - On the other hand, if all possible pairs of sequences `(S, S')` produce the same result, + * then `x` and `y` are the same. + * + * Based on this definition, let's see if the expressions + * + * {{{ + * val x = new BankAccount + * val y = new BankAccount + * }}} + * + * Let's follow the definitions by a test sequence: + * + * {{{ + * val x = new BankAccount + * val y = new BankAccount + * x deposit 30 + * y withdraw 20 // java.lang.Error: insufficient funds + * }}} + * + * Now rename all occurrences of `y` with `x` in this sequence. We obtain: + */ + def observationalEquivalence(res0: Int): Unit = { + val x = new BankAccount + val y = new BankAccount + x deposit 30 + x withdraw 20 shouldBe res0 + } + + /** + * The final results are different. We conclude that `x` and `y` + * are not the same. + * + * = Establishing Operational Equivalence = + * + * On the other hand, if we define + * + * {{{ + * val x = new BankAccount + * val y = x + * }}} + * + * then no sequence of operations can distinguish between `x` and `y`, so + * `x` and `y` are the same in this case. + * + * = Assignment and Substitution Model = + * + * The preceding examples show that our model of computation by + * substitution cannot be used. + * + * Indeed, according to this model, one can always replace the name of a + * value by the expression that defines it. For example, in + * + * {{{ + * val x = new BankAccount + * val y = x + * }}} + * + * the `x` in the definition of `y` could be replaced by `new BankAccount`. + * + * But we have seen that this change leads to a different program! + * + * The substitution model ceases to be valid when we add the assignment. + * + * It is possible to adapt the substitution model by introducing a ''store'', + * but this becomes considerably more complicated. + * + * = Imperative Loops = + * + * In the first sections, we saw how to write loops using recursion. + * + * == While-Loops == + * + * We can also write loops with the `wile` keyword: + * + * {{{ + * def power (x: Double, exp: Int): Double = { + * var r = 1.0 + * var i = exp + * while (i > 0) { r = r * x; i = i - 1 } + * r + * } + * }}} + * + * As long as the condition of a ''while'' statement is `true`, + * its body is evaluated. + * + * == For-Loops == + * + * In Scala there is a kind of `for` loop: + * + * {{{ + * for (i <- 1 until 3) { System.out.print(i + " ") } + * }}} + * + * This displays `1 2`. + * + * For-loops translate similarly to for-expressions, but using the + * `foreach` combinator instead of `map` and `flatMap`. + * + * `foreach` is defined on collections with elements of type `A` as follows: + * + * {{{ + * def foreach(f: A => Unit): Unit = + * // apply `f` to each element of the collection + * }}} + * + * Example: + * + * {{{ + * for (i <- 1 until 3; j <- "abc") println(i + " " + j) + * }}} + * + * translates to: + * + * {{{ + * (1 until 3) foreach (i => "abc" foreach (j => println(i + " " + j))) + * }}} + */ + def nothing(): Unit = () } diff --git a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala index 28a44639..4296433c 100644 --- a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala +++ b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala @@ -533,20 +533,6 @@ object ObjectOrientedProgramming extends ScalaTutorialSection { * * Singleton objects are values, so `Empty` evaluates to itself. * - -Exercise -======== - -Write a method `union` for forming the union of two sets. You should -implement the following abstract class. - - abstract class IntSet { - def incl(x: Int): IntSet - def contains(x: Int): Boolean - def union(other: IntSet): IntSet - } - - * * = Dynamic Binding = * * Object-oriented languages (including Scala) implement @@ -571,7 +557,7 @@ implement the following abstract class. * * = Traits = * - * In Java, as well as in Scala, a class can only have one superclass. + * In Scala, a class can only have one superclass. * * But what if a class has several natural supertypes to which it conforms * or from which it wants to inherit code? @@ -596,7 +582,7 @@ implement the following abstract class. * class Square extends Shape with Planar with Movable … * }}} * - * Traits cannot have (value) parameters, only classes can. + * On the other hand, traits cannot have (value) parameters, only classes can. * * = Scala's Class Hierarchy = * diff --git a/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala b/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala new file mode 100644 index 00000000..ee91f23a --- /dev/null +++ b/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala @@ -0,0 +1,15 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class ImperativeProgrammingSpec extends Spec with Checkers { + + def `check observational equivalence`: Unit = { + check(Test.testSuccess(ImperativeProgramming.observationalEquivalence _, 10 :: HNil)) + } + +} From 4c3b32172c2bc257afe5cee3a852ac29263a800b Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 24 Nov 2016 14:34:26 +0100 Subject: [PATCH 14/19] Add ClassesVsCaseClasses and LazyEvaluation sections --- src/main/scala/scalatutorial/aux/Note.scala | 3 + .../sections/ClassesVsCaseClasses.scala | 169 +++++++++++++ .../sections/LazyEvaluation.scala | 228 ++++++++++++++++++ .../sections/SyntacticConveniences.scala | 92 +++++++ .../sections/ClassesVsCaseClassesSpec.scala | 19 ++ .../sections/LazyEvaluationSpec.scala | 19 ++ 6 files changed, 530 insertions(+) create mode 100644 src/main/scala/scalatutorial/aux/Note.scala create mode 100644 src/test/scala/scalatutorial/sections/ClassesVsCaseClassesSpec.scala create mode 100644 src/test/scala/scalatutorial/sections/LazyEvaluationSpec.scala diff --git a/src/main/scala/scalatutorial/aux/Note.scala b/src/main/scala/scalatutorial/aux/Note.scala new file mode 100644 index 00000000..992a1b98 --- /dev/null +++ b/src/main/scala/scalatutorial/aux/Note.scala @@ -0,0 +1,3 @@ +package scalatutorial.aux + +case class Note(name: String, duration: String, octave: Int) diff --git a/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala b/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala index a934a237..dd2582c4 100644 --- a/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala +++ b/src/main/scala/scalatutorial/sections/ClassesVsCaseClasses.scala @@ -1,6 +1,175 @@ package scalatutorial.sections +import scalatutorial.aux.{BankAccount, Note} + /** @param name classes_vs_case_classes */ object ClassesVsCaseClasses extends ScalaTutorialSection { + /** + * In the previous sections we have seen how case classes could be + * used to achieve information aggregation, and also how classes + * could be used to achieve data abstraction or to define stateful + * objects. + * + * What are the relationship between classes and case classes? How + * do they differ? + * + * = Creation and Manipulation = + * + * Remember the class definition of `BankAccount`: + * + * {{{ + * class BankAccount { + * + * private var balance = 0 + * + * def deposit(amount: Int): Unit = { + * if (amount > 0) balance = balance + amount + * } + * + * def withdraw(amount: Int): Int = + * if (0 < amount && amount <= balance) { + * balance = balance - amount + * balance + * } else throw new Error("insufficient funds") + * } + * }}} + * + * And the case class definition of `Note`: + * + * {{{ + * case class Note(name: String, duration: String, octave: Int) + * }}} + * + * Let’s create some instances of `BankAccount` and `Note` and manipulate them: + */ + def creationAndManipulation(res0: String): Unit = { + val aliceAccount = new BankAccount + val c3 = Note("C", "Quarter", 3) + + c3.name shouldBe res0 + } + /** + * We see that creating a class instance requires the keyword `new`, whereas + * this is not required for case classes. + * + * Also, we see that the case class constructor parameters are promoted to + * members, whereas this is not the case with regular classes. + * + * = Equality = + * + */ + def equality(res0: Boolean, res1: Boolean): Unit = { + val aliceAccount = new BankAccount + val bobAccount = new BankAccount + + aliceAccount == bobAccount shouldBe res0 + + val c3 = Note("C", "Quarter", 3) + val cThree = Note("C", "Quarter", 3) + + c3 == cThree shouldBe res1 + } + + /** + * In the above example, the same definitions of bank accounts lead to different + * values, whereas the same definitions of notes lead to equal values. + * + * As we have seen in the previous sections, stateful classes introduce a notion of ''identity'' + * that does not exist in case classes. Indeed, the value of `BankAccount` can change over + * time whereas the value of a `Note` is immutable. + * + * In Scala, by default, comparing objects will compare their identity, but in the + * case of case class instances, the equality is redefined to compare the values of + * the aggregated information. + * + * = Pattern Matching = + * + * We saw how pattern matching can be used to extract information from a case class instance: + * + * {{{ + * c3 match { + * case Note(name, duration, octave) => s"The duration of c3 is $duration" + * } + * }}} + * + * By default, pattern matching does not work with regular classes. + * + * = Extensibility = + * + * A class can extend another class, whereas a case class can not extend + * another case class (because it would not be possible to correctly + * implement their equality). + * + * = Case Classes Encoding = + * + * We saw the main differences between classes and case classes. + * + * It turns out that case classes are just a special case of classes, + * whose purpose is to aggregate several values into a single value. + * + * The Scala language provides explicit support for this use case + * because it is very common in practice. + * + * So, when we define a case class, the Scala compiler defines a class + * enhanced with some more methods and a companion object. + * + * For instance, the following case class definition: + * + * {{{ + * case class Note(name: String, duration: String, octave: Int) + * }}} + * + * Expands to the following class definition: + * + * {{{ + * class Note(_name: String, _duration: String, _octave: Int) extends Serializable { + * + * // Constructor parameters are promoted to members + * val name = _name + * val duration = _duration + * val octave = _octave + * + * // Equality redefinition + * override def equals(other: Any): Boolean = other match { + * case that: Note => + * (that canEqual this) && + * name == that.name && + * duration == that.duration && + * octave == that.octave + * case _ => false + * } + * + * def canEqual(other: Any): Boolean = other.isInstanceOf[Note] + * + * // Java hashCode redefinition according to equality + * override def hashCode(): Int = { + * val state = Seq(name, duration, octave) + * state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) + * } + * + * // toString redefinition to return the value of an instance instead of its memory addres + * override def toString = s"Note($name, $duration, $octave)" + * + * // Create a copy of a case class, with potentially modified field values + * def copy(name: String = name, duration: String = duration, octave: Int = octave): Note = + * new Note(name, duration, octave) + * + * } + * + * object Note { + * + * // Constructor that allows the omission of the `new` keyword + * def apply(name: String, duration: String, octave: Int): Note = + * new Note(name, duration, octave) + * + * // Extractor for pattern matching + * def unapply(note: Note): Option[(String, String, Int)) = + * if (note eq null) None + * else Some((note.name, note.duration, note.octave)) + * + * } + * }}} + */ + def nothing(): Unit = () } diff --git a/src/main/scala/scalatutorial/sections/LazyEvaluation.scala b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala index 40331569..d2b9a603 100644 --- a/src/main/scala/scalatutorial/sections/LazyEvaluation.scala +++ b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala @@ -3,4 +3,232 @@ package scalatutorial.sections /** @param name lazy_evaluation */ object LazyEvaluation extends ScalaTutorialSection { + /** + * = Motivation = + * + * Consider the following program that finds the second prime number between 1000 and 10000: + * + * {{{ + * ((1000 to 10000) filter isPrime)(1) + * }}} + * + * This is ''much'' shorter than the recursive alternative: + * + * {{{ + * def secondPrime(from: Int, to: Int) = nthPrime(from, to, 2) + * def nthPrime(from: Int, to: Int, n: Int): Int = + * if (from >= to) throw new Error("no prime") + * else if (isPrime(from)) + * if (n == 1) from else nthPrime(from + 1, to, n - 1) + * else nthPrime(from + 1, to, n) + * }}} + * + * But from a standpoint of performance, the first version is pretty bad; it constructs + * ''all'' prime numbers between `1000` and `10000` in a list, but only ever looks at + * the first two elements of that list. + * + * Reducing the upper bound would speed things up, but risks that we miss the + * second prime number all together. + * + * = Delayed Evaluation = + * + * However, we can make the short-code efficient by using a trick: + * + * - 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`. + * + * Streams are similar to lists, but their tail is evaluated only ''on demand''. + * + * = Defining Streams = + * + * Streams are defined from a constant `Stream.empty` and a constructor `Stream.cons`. + * + * For instance, + * + * {{{ + * val xs = Stream.cons(1, Stream.cons(2, Stream.empty)) + * }}} + * + * = Stream Ranges = + * + * Let's try to write a function that returns a `Stream` 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)) + * }}} + * + * Compare to the same function that produces a list: + * + * {{{ + * def listRange(lo: Int, hi: Int): List[Int] = + * if (lo >= hi) Nil + * else lo :: listRange(lo + 1, hi) + * }}} + * + * 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. + * - The other elements are only computed when they are needed, where + * “needed” means that someone calls `tail` on the stream. + * + * = Methods on Streams = + * + * `Stream` supports almost all methods of `List`. + * + * For instance, to find the second prime number between 1000 and 10000: + * + * {{{ + * (streamRange(1000, 10000) filter isPrime)(1) + * }}} + * + * The one major exception is `::`. + * + * `x :: xs` always produces a list, never a stream. + * + * There is however an alternative operator `#::` which produces a stream. + * + * {{{ + * x #:: xs == Stream.cons(x, xs) + * }}} + * + * `#::` can be used in expressions as well as patterns. + * + * = Implementation of Streams = + * + * The implementation of streams is quite close to the one of lists. + * + * Here's the trait `Stream`: + * + * {{{ + * trait Stream[+A] extends Seq[A] { + * def isEmpty: Boolean + * def head: A + * def tail: Stream[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. + * 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()" + * } + * } + * }}} + * + * The only important difference between the implementations of `List` and `Stream` + * concern `tl`, the second parameter of `Stream.cons`. + * + * For streams, this is a by-name parameter: the type of `tl` 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. + * + * Instead, it will be evaluated each time someone calls `tail` on a `Stream` object. + * + * The other stream methods are implemented analogously to their list counterparts. + * + * 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) + * } + * }}} + * + * = Exercise = + * + * Consider the following modification of `streamRange`. When you write + * `streamRange(1, 10).take(3).toList` what is the value of `rec`? + */ + def streamRangeExercise(res0: Int): Unit = { + var rec = 0 + def streamRange(lo: Int, hi: Int): Stream[Int] = { + rec = rec + 1 + if (lo >= hi) Stream.empty + else Stream.cons(lo, streamRange(lo + 1, hi)) + } + streamRange(1, 10).take(3).toList + rec shouldBe res0 + } + /** + * = Lazy Evaluation = + * + * The proposed `Stream` implementation suffers from a serious potential performance + * problem: If `tail` is called several times, the corresponding stream + * will be recomputed each time. + * + * This problem can be avoided by storing the result of the first + * evaluation of `tail` and re-using the stored result instead of recomputing `tail`. + * + * This optimization is sound, since in a purely functional language an + * expression produces the same result each time it is evaluated. + * + * We call this scheme ''lazy evaluation'' (as opposed to ''by-name evaluation'' in + * the case where everything is recomputed, and ''strict evaluation'' for normal + * parameters and `val` definitions.) + * + * == Lazy Evaluation in Scala == + * + * Haskell is a functional programming language that uses lazy evaluation by default. + * + * Scala uses strict evaluation by default, but allows lazy evaluation of value definitions + * with the `lazy val` form: + * + * {{{ + * lazy val x = expr + * }}} + * + * == Exercise == + */ + def lazyVal(res0: String): Unit = { + val builder = new StringBuilder + + val x = { builder += 'x'; 1 } + lazy val y = { builder += 'y'; 2 } + def z = { builder += 'z'; 3 } + + z + y + x + z + y + x + + builder.result() shouldBe res0 + } + + /** + * = 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 + * … + * } + * }}} + */ + def nothing(): Unit = () } diff --git a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala index c8ef2bd4..adaddb94 100644 --- a/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala +++ b/src/main/scala/scalatutorial/sections/SyntacticConveniences.scala @@ -96,6 +96,98 @@ object SyntacticConveniences extends ScalaTutorialSection { } /** + * = Functions as Objects = + * + * We have seen that Scala's numeric types and the `Boolean` + * type can be implemented like normal classes. + * + * But what about functions? + * + * In fact function values ''are'' treated as objects in Scala. + * + * The function type `A => B` is just an abbreviation for the class + * `scala.Function1[A, B]`, which is defined as follows. + * + * {{{ + * package scala + * trait Function1[A, B] { + * def apply(x: A): B + * } + * }}} + * + * So functions are objects with `apply` methods. + * + * There are also traits `Function2`, `Function3`, ... for functions which take more parameters (currently up to 22). + * + * == Expansion of Function Values == + * + * An anonymous function such as + * + * {{{ + * (x: Int) => x * x + * }}} + * + * is expanded to: + * + * {{{ + * { + * class AnonFun extends Function1[Int, Int] { + * def apply(x: Int) = x * x + * } + * new AnonFun + * } + * }}} + * + * or, shorter, using ''anonymous class syntax'': + * + * {{{ + * new Function1[Int, Int] { + * def apply(x: Int) = x * x + * } + * }}} + * + * == Expansion of Function Calls == + * + * A function call, such as `f(a, b)`, where `f` is a value of some class + * type, is expanded to: + * + * {{{ + * f.apply(a, b) + * }}} + * + * So the OO-translation of: + * + * {{{ + * val f = (x: Int) => x * x + * f(7) + * }}} + * + * would be: + * + * {{{ + * val f = new Function1[Int, Int] { + * def apply(x: Int) = x * x + * } + * f.apply(7) + * }}} + * + * == Functions and Methods == + * + * Note that a method such as + * + * {{{ + * def f(x: Int): Boolean = … + * }}} + * + * is not itself a function value. + * + * But if `f` is used in a place where a Function type is expected, it is + * converted automatically to the function value + * + * {{{ + * (x: Int) => f(x) + * }}} + * * = `for` expressions = * * You probably noticed that several data types of the standard library diff --git a/src/test/scala/scalatutorial/sections/ClassesVsCaseClassesSpec.scala b/src/test/scala/scalatutorial/sections/ClassesVsCaseClassesSpec.scala new file mode 100644 index 00000000..ba8b9d08 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/ClassesVsCaseClassesSpec.scala @@ -0,0 +1,19 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class ClassesVsCaseClassesSpec extends Spec with Checkers { + + def `check creation and manipulation`: Unit = { + check(Test.testSuccess(ClassesVsCaseClasses.creationAndManipulation _, "C" :: HNil)) + } + + def `check equality`: Unit = { + check(Test.testSuccess(ClassesVsCaseClasses.equality _, false :: true :: HNil)) + } + +} diff --git a/src/test/scala/scalatutorial/sections/LazyEvaluationSpec.scala b/src/test/scala/scalatutorial/sections/LazyEvaluationSpec.scala new file mode 100644 index 00000000..ee4e8094 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/LazyEvaluationSpec.scala @@ -0,0 +1,19 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class LazyEvaluationSpec extends Spec with Checkers { + + def `check stream range`: Unit = { + check(Test.testSuccess(LazyEvaluation.streamRangeExercise _, 3 :: HNil)) + } + + def `check lazy val`: Unit = { + check(Test.testSuccess(LazyEvaluation.lazyVal _, "xzyz" :: HNil)) + } + +} From 14b2534fa0ee9d901528c4012529ac2b8b8b03fc Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Thu, 24 Nov 2016 16:36:57 +0100 Subject: [PATCH 15/19] Add PolymorphicTypes section --- .../public/scala_tutorial/animals.svg | 4 + .../scala/scalatutorial/ScalaTutorial.scala | 2 +- .../scala/scalatutorial/aux/animals.scala | 15 + .../sections/LazyEvaluation.scala | 6 +- .../sections/PolymorphicTypes.scala | 419 ++++++++++++++++++ 5 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/public/scala_tutorial/animals.svg create mode 100644 src/main/scala/scalatutorial/aux/animals.scala diff --git a/src/main/resources/public/scala_tutorial/animals.svg b/src/main/resources/public/scala_tutorial/animals.svg new file mode 100644 index 00000000..d1bb2c56 --- /dev/null +++ b/src/main/resources/public/scala_tutorial/animals.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/scala/scalatutorial/ScalaTutorial.scala b/src/main/scala/scalatutorial/ScalaTutorial.scala index 31d37ea0..5d8c2451 100644 --- a/src/main/scala/scalatutorial/ScalaTutorial.scala +++ b/src/main/scala/scalatutorial/ScalaTutorial.scala @@ -27,8 +27,8 @@ object ScalaTutorial extends Library { ObjectOrientedProgramming, ImperativeProgramming, ClassesVsCaseClasses, - LazyEvaluation, PolymorphicTypes, + LazyEvaluation, TypeClasses ) } diff --git a/src/main/scala/scalatutorial/aux/animals.scala b/src/main/scala/scalatutorial/aux/animals.scala new file mode 100644 index 00000000..d3e13fe6 --- /dev/null +++ b/src/main/scala/scalatutorial/aux/animals.scala @@ -0,0 +1,15 @@ +package scalatutorial.aux + +trait Animal { + def fitness: Int +} + +trait Reptile extends Animal + +trait Mammal extends Animal + +trait Zebra extends Mammal { + def zebraCount: Int +} + +trait Giraffe extends Mammal diff --git a/src/main/scala/scalatutorial/sections/LazyEvaluation.scala b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala index d2b9a603..eba8be17 100644 --- a/src/main/scala/scalatutorial/sections/LazyEvaluation.scala +++ b/src/main/scala/scalatutorial/sections/LazyEvaluation.scala @@ -106,10 +106,10 @@ object LazyEvaluation extends ScalaTutorialSection { * Here's the trait `Stream`: * * {{{ - * trait Stream[+A] extends Seq[A] { + * trait Stream[+T] extends Seq[T] { * def isEmpty: Boolean - * def head: A - * def tail: Stream[A] + * def head: T + * def tail: Stream[T] * … * } * }}} diff --git a/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala index eed9ab69..a08b8859 100644 --- a/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala +++ b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala @@ -3,4 +3,423 @@ package scalatutorial.sections /** @param name polymorphic_types */ object PolymorphicTypes extends ScalaTutorialSection { + /** + * Remember the definition of `IntSet` (in section Object Oriented Programming): + * + * {{{ + * abstract class IntSet { + * def incl(x: Int): IntSet + * def contains(x: Int): Boolean + * } + * }}} + * + * = Type Parameters = + * + * It seems too narrow to define only sets with `Int` elements. + * + * We'd need another class hierarchy for `Double` lists, and so on, + * one for each possible element type. + * + * We can generalize the definition using a ''type parameter'': + * + * {{{ + * abstract class Set[A] { + * def incl(a: A): Set[A] + * def contains(a: A): Boolean + * } + * class Empty[A] extends Set[A] { + * … + * } + * class NonEmpty[A](elem: A, left: Set[A], right: Set[A]) extends Set[A] { + * … + * } + * }}} + * + * Type parameters are written in square brackets, e.g. `[A]`. + * + * = Generic Functions = + * + * Like classes, functions can have type parameters. + * + * For instance, here is a function that creates a set consisting of a single element. + * + * {{{ + * def singleton[A](elem: A) = new NonEmpty[A](elem, new Empty[A], new Empty[A]) + * }}} + * + * We can then write: + * + * {{{ + * singleton[Int](1) + * singleton[Boolean](true) + * }}} + * + * = Type Inference = + * + * In fact, the Scala compiler can usually deduce the correct type + * parameters from the value arguments of a function call. + * + * So, in most cases, type parameters can be left out. You could also write: + * + * {{{ + * singleton(1) + * singleton(true) + * }}} + * + * = Types and Evaluation = + * + * Type parameters do not affect evaluation in Scala. + * + * We can assume that all type parameters and type arguments are removed + * before evaluating the program. + * + * This is also called ''type erasure''. + * + * Languages that use type erasure include Java, Scala, Haskell, ML, OCaml. + * + * Some other languages keep the type parameters around at run time, these include C++, C#, F#. + * + * = Polymorphism = + * + * Polymorphism means that a function type comes "in many forms". + * + * In programming it means that + * + * - the function can be applied to arguments of many types, or + * - the type can have instances of many types. + * + * We have seen two principal forms of polymorphism: + * + * - subtyping: instances of a subclass can be passed to a base class + * - generics: instances of a function or class are created by type parameterization. + * + * The remaining subsections compare their interaction. + * + * Consider the following class hierarchy: + * + * + * + * {{{ + * trait Animal { + * def fitness: Int + * } + * + * trait Reptile extends Animal + * + * trait Mammal extends Animal + * + * trait Zebra extends Mammal { + * def zebraCount: Int + * } + * + * trait Giraffe extends Mammal + * }}} + * + * = Type Bounds = + * + * Consider the method `selection` that takes two animals as parameters + * and returns the one with the highest `fitness` value: + * + * What would be the best type you can give to `selection`? Maybe: + * + * {{{ + * def selection(a1: Animal, a2: Animal): Animal + * }}} + * + * In most situations this is fine, but can one be more precise? + * + * One might want to express that `selection` + * takes `Zebra`s to `Zebra`s and `Reptile`s to `Reptile`s. + * + * == Upper Bounds == + * + * A way to express this is: + * + * {{{ + * def selection[A <: Animal](a1: A, a2: A): A = + * if (a1.fitness > a2.fitness) a1 else a2 + * }}} + * + * Here, “`<: Animal`” is an ''upper bound'' of the type parameter `A`. + * + * It means that `A` can be instantiated only to types that conform to `Animal`. + * + * Generally, the notation + * + * - `A <: B` means: ''`A` is a subtype of `B`'', and + * - `A >: B` means: ''`A` is a supertype of `B`'', or ''`B` is a subtype of `A`''. + * + * == Lower Bounds == + * + * You can also use a lower bound for a type variable. + * + * {{{ + * A >: Reptile + * }}} + * + * The type parameter `A` that can range only over ''supertypes'' of `Reptile`. + * + * So `A` could be one of `Reptile`, `Animal`, `AnyRef`, or `Any`. + * + * (We will see later on in this section where lower bounds are useful). + * + * == Mixed Bounds == + * + * Finally, it is also possible to mix a lower bound with an upper bound. + * + * For instance, + * + * {{{ + * A >: Zebra <: Animal + * }}} + * + * would restrict `A` any type on the interval between `Zebra` and `Animal`. + * + * = Covariance = + * + * There's another interaction between subtyping and type parameters we + * need to consider. + * + * Consider the following type modeling a field containing an animal: + * + * {{{ + * trait Field[A] { + * def get: A // returns the animal that lives in this field + * } + * }}} + * + * Given + * + * {{{ + * Zebra <: Mammal + * }}} + * + * is + * + * {{{ + * Field[Zebra] <: Field[Mammal] + * }}} + * + * ? + * + * Intuitively, this makes sense: a field containing a zebra is a special case of a field + * containing an arbitrary mammal. + * + * We call types for which this relationship holds ''covariant'' + * because their subtyping relationship varies with the type parameter. + * + * Does covariance make sense for all types, not just for `Field`? + * + * === Arrays === + * + * For perspective, let's look at arrays in Java (and C#). + * + * Reminder: + * + * - An array of `T` elements is written `T[]` in Java. + * - In Scala we use parameterized type syntax `Array[T]` to refer to the same type. + * + * Arrays in Java are covariant, so one would have: + * + * {{{ + * Zebra[] <: Mammal[] + * }}} + * + * But covariant array typing causes problems. + * + * To see why, consider the Java code below: + * + * {{{ + * Zebra[] zebras = new Zebra[]{ new Zebra() } // Array containing 1 `Zebra` + * Mammal[] mammals = zebras // Allowed because arrays are covariant in Java + * mammals[0] = new Giraffe() // Allowed because a `Giraffe` is a subtype of `Mammal` + * Zebra zebra = zebras[0] // Get the first `Zebra` … which is actually a `Giraffe`! + * }}} + * + * It looks like we assigned in the last line a `Giraffe` to a + * variable of type `Zebra`! + * + * What went wrong? + * + * === The Liskov Substitution Principle === + * + * The following principle, stated by Barbara Liskov, tells us when a + * type can be a subtype of another. + * + * If `A <: B`, then everything one can to do with a value of type `B` one should also + * be able to do with a value of type `A`. + * + * The problematic array example would be written as follows in Scala: + * + * {{{ + * val zebras: Array[Zebra] = Array(new Zebra) + * val mammals: Array[Mammal] = zebras + * mammals(0) = new Giraffe + * val zebra: Zebra = zebras(0) + * }}} + * + * If you try to compile this example you will get a compile error at line 2: + * + * {{{ + * type mismatch; + * found : Array[Zebra] + * required: Array[Mammal] + * }}} + * + * = Variance = + * + * We have seen that some types should be covariant whereas + * others should not. + * + * Roughly speaking, a type that accepts mutations of its elements should + * not be covariant. + * + * But immutable types can be covariant, if some conditions + * on methods are met. + * + * = Definition of Variance = + * + * Say `C[T]` is a parameterized type and `A`, `B` are types such that `A <: B`. + * + * In general, there are ''three'' possible relationships between `C[A]` and `C[B]`: + * + * - `C[A] <: C[B]`, `C` is ''covariant'', + * - `C[A] >: C[B]`, `C` is ''contravariant'', + * - neither `C[A]` nor `C[B]` is a subtype of the other, `C` is ''nonvariant''. + * + * Scala lets you declare the variance of a type by annotating the type parameter: + * + * - `class C[+A] { … }`, `C` is ''covariant'', + * - `class C[-A] { … }`, `C` is ''contravariant'', + * - `class C[A] { … }`, `C` is ''nonvariant''. + * + * == Typing Rules for Functions == + * + * Generally, we have the following rule for subtyping between function types: + * + * If `A2 <: A1` and `B1 <: B2`, then + * + * `A1 => B1 <: A2 => B2` + * + * So functions are ''contravariant'' in their argument type(s) and + * ''covariant'' in their result type. + * + * This leads to the following revised definition of the `Function1` trait: + * + * {{{ + * trait Function1[-T, +U] { + * def apply(x: T): U + * } + * }}} + * + * == Contravariance Example == + * + * Consider the following type modeling a veterinary: + * + * {{{ + * trait Vet[A] { + * def treat(a: A): Unit // Treats an animal of type `A` + * } + * }}} + * + * In such a case, intuitively, it makes sense to have `Vet[Mammal] <: Vet[Zebra]` because + * a vet that can treat any mammal is able to to treat a zebra in particular. This is + * an example of a contravariant type. + * + * == Variance Checks == + * + * We have seen in the array example that the combination of covariance with + * certain operations is unsound. + * + * In the case of `Array`, the problematic combination is: + * - the covariant type parameter `T` + * - which appears in parameter position of the method `update`. + * + * The Scala compiler will check that there are no problematic combinations when + * compiling a class with variance annotations. + * + * Roughly, + * + * - ''covariant'' type parameters can only appear in method results. + * - ''contravariant'' type parameters can only appear in method parameters. + * - ''invariant'' type parameters can appear anywhere. + * + * The precise rules are a bit more involved, fortunately the Scala compiler performs them for us. + * + * === Variance-Checking the Function Trait === + * + * Let's have a look again at Function1: + * + * {{{ + * trait Function1[-T, +U] { + * def apply(x: T): U + * } + * }}} + * + * Here, + * + * - `T` is contravariant and appears only as a method parameter type + * - `U` is covariant and appears only as a method result type + * + * So the method is checks out OK. + * + * = Making Classes Covariant = + * + * Sometimes, we have to put in a bit of work to make a class covariant. + * + * Consider adding a `prepend` method to `Stream` which prepends a given + * element, yielding a new stream. + * + * A first implementation of `prepend` could look like this: + * + * {{{ + * trait Stream[+T] { + * def prepend(elem: T): Stream[T] = Stream.cons(elem, this) + * } + * }}} + * + * But that does not work! + * + * Why does the above code not type-check? + * + * `prepend` fails variance checking. + * + * Indeed, the compiler is right to throw out `Stream` with `prepend`, + * because it violates the Liskov Substitution Principle: + * + * Here's something one can do with a stream `mammals` of type `Stream[Mammal]`: + * + * {{{ + * mammals.prepend(new Giraffe) + * }}} + * + * But the same operation on a list `zebras` of type + * `Stream[Zebra]` would lead to a type error: + * + * {{{ + * zebras.prepend(new Giraffe) + * ^ type mismatch + * required: Zebra + * found: Giraffe + * }}} + * + * So, `Stream[Zebra]` cannot be a subtype of `Stream[Mammal]`. + * + * But `prepend` is a natural method to have on immutable lists! + * + * How can we make it variance-correct? + * + * We can use a ''lower bound'': + * + * {{{ + * def prepend [U >: T](elem: U): Stream[U] = Stream.cons(elem, this) + * }}} + * + * This passes variance checks, because: + * + * - covariant type parameters may appear in lower bounds of method type parameters + * - contravariant type parameters may appear in upper bounds of method + */ + def nothing(): Unit = () + } From b6ef5574cf9a32a04613e0e37d8135da8e57b25d Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 25 Nov 2016 13:59:11 +0100 Subject: [PATCH 16/19] Add TypeClass section --- .../scala/scalatutorial/aux/Rational.scala | 11 + .../scala/scalatutorial/aux/sorting.scala | 20 ++ .../sections/StandardLibrary.scala | 4 +- .../scalatutorial/sections/TypeClasses.scala | 193 ++++++++++++++++++ .../sections/TypeClassesSpec.scala | 19 ++ 5 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/scalatutorial/aux/Rational.scala create mode 100644 src/main/scala/scalatutorial/aux/sorting.scala create mode 100644 src/test/scala/scalatutorial/sections/TypeClassesSpec.scala diff --git a/src/main/scala/scalatutorial/aux/Rational.scala b/src/main/scala/scalatutorial/aux/Rational.scala new file mode 100644 index 00000000..4dd82aa4 --- /dev/null +++ b/src/main/scala/scalatutorial/aux/Rational.scala @@ -0,0 +1,11 @@ +package scalatutorial.aux + +class Rational(x: Int, y: Int) { + + private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + private val g = gcd(x, y) + + lazy val numer: Int = x / g + lazy val denom: Int = y / g + +} diff --git a/src/main/scala/scalatutorial/aux/sorting.scala b/src/main/scala/scalatutorial/aux/sorting.scala new file mode 100644 index 00000000..37bfd31a --- /dev/null +++ b/src/main/scala/scalatutorial/aux/sorting.scala @@ -0,0 +1,20 @@ +package scalatutorial.aux + +object Sorting { + + def insertionSort[A](xs: List[A])(implicit ord: Ordering[A]): List[A] = { + def insert(y: A, ys: List[A]): List[A] = + ys match { + case List() => y :: List() + case z :: zs => + if (ord.lt(y, z)) y :: z :: zs + else z :: insert(y, zs) + } + + xs match { + case List() => List() + case y :: ys => insert(y, insertionSort(ys)) + } + } + +} \ No newline at end of file diff --git a/src/main/scala/scalatutorial/sections/StandardLibrary.scala b/src/main/scala/scalatutorial/sections/StandardLibrary.scala index 0b9b10c4..a597effd 100644 --- a/src/main/scala/scalatutorial/sections/StandardLibrary.scala +++ b/src/main/scala/scalatutorial/sections/StandardLibrary.scala @@ -103,9 +103,9 @@ object StandardLibrary extends ScalaTutorialSection { * This idea describes ''Insertion Sort'': * * {{{ - * def isort(xs: List[Int]): List[Int] = xs match { + * def insertionSort(xs: List[Int]): List[Int] = xs match { * case List() => List() - * case y :: ys => insert(y, isort(ys)) + * case y :: ys => insert(y, insertionSort(ys)) * } * }}} * diff --git a/src/main/scala/scalatutorial/sections/TypeClasses.scala b/src/main/scala/scalatutorial/sections/TypeClasses.scala index 8f5a73cc..b924b710 100644 --- a/src/main/scala/scalatutorial/sections/TypeClasses.scala +++ b/src/main/scala/scalatutorial/sections/TypeClasses.scala @@ -1,6 +1,199 @@ package scalatutorial.sections +import scalatutorial.aux.Rational +import scalatutorial.aux.Sorting.insertionSort + /** @param name type_classes */ object TypeClasses extends ScalaTutorialSection { + /** + * Remember the sorting function: + * + * {{{ + * def insertionSort(xs: List[Int]): List[Int] = { + * def insert(y: Int, ys: List[Int]): List[Int] = + * ys match { + * case List() => y :: List() + * case z :: zs => + * if (y < z) y :: z :: zs + * else z :: insert(y, zs) + * } + * + * xs match { + * case List() => List() + * case y :: ys => insert(y, insertionSort(ys)) + * } + * } + * + * }}} + * + * How to parameterize `insertionSort` so that it can also be used for + * lists with elements other than `Int` (like, for instance, `Rational`)? + * + * {{{ + * def insertionSort[T](xs: List[T]): List[T] = ... + * }}} + * + * The above attempt does not work, because the comparison `<` in `insert` + * is not defined for arbitrary types `T`. + * + * Idea: parameterize `insert` with the necessary comparison function. + * + * = Parameterization of Sort = + * + * The most flexible design is to make the function `insertionSort` + * polymorphic and to pass the comparison operation as an additional + * parameter: + * + * {{{ + * def insertionSort[T](xs: List[T])(lessThan: (T, T) => Boolean) = { + * def insert(y: Int, ys: List[Int]): List[Int] = + * ys match { + * … + * case z :: zs => + * if (lessThan(y, z)) y :: z :: zs + * else … + * } + * + * xs match { + * … + * case y :: ys => insert(y, insertionSort(ys)(lessThan)) + * } + * } + * + * = Calling Parameterized Sort = + * + * We can now call `insertionSort` as follows: + * + * {{{ + * val nums = List(-5, 6, 3, 2, 7) + * val fruit = List("apple", "pear", "orange", "pineapple") + * + * insertionSort(nums)((x: Int, y: Int) => x < y) + * insertionSort(fruit)((x: String, y: String) => x.compareTo(y) < 0) + * }}} + * + * Or, since parameter types can be inferred from the call `insertionSort(xs)`: + * + * {{{ + * insertionSort(nums)((x, y) => x < y) + * }}} + * + * = Parametrization with Ordered = + * + * There is already a class in the standard library that represents orderings. + * + * {{{ + * scala.math.Ordering[T] + * }}} + * + * provides ways to compare elements of type `T`. So instead of + * parameterizing with the `lessThan` operation directly, we could parameterize + * with `Ordering` instead: + * + * {{{ + * def insertionSort[T](xs: List[T])(ord: Ordering[T]): List[T] = { + * def insert(y: Int, ys: List[Int]): List[Int] = + * … if (ord.lt(y, z)) … + * + * … insert(y, insertionSort(ys)(ord)) … + * } + * }}} + * + * = Ordered Instances: = + * + * Calling the new `insertionSort` can be done like this: + * + * {{{ + * insertionSort(nums)(Ordering.Int) + * insertionSort(fruits)(Ordering.String) + * }}} + * + * This makes use of the values `Int` and `String` defined in the + * `scala.math.Ordering` object, which produce the right orderings on + * integers and strings. + * + * = Implicit Parameters = + * + * Problem: Passing around `lessThan` or `ord` values is cumbersome. + * + * We can avoid this by making `ord` an implicit parameter: + * + * {{{ + * def insertionSort[T](xs: List[T])(implicit ord: Ordering[T]): List[T] = { + * def insert(y: Int, ys: List[Int]): List[Int] = + * … if (ord.lt(y, z)) … + * + * … insert(y, insertionSort(ys)) … + * } + * }}} + * + * Then calls to `insertionSort` can avoid the ordering parameters: + * + * {{{ + * insertionSort(nums) + * insertionSort(fruits) + * }}} + * + * The compiler will figure out the right implicit to pass based on the + * demanded type. + * + * = Rules for Implicit Parameters = + * + * Say, a function takes an implicit parameter of type `T`. + * + * The compiler will search an implicit definition that + * + * - is marked `implicit` + * - has a type compatible with `T` + * - is visible at the point of the function call, or is defined + * in a companion object associated with `T`. + * + * If there is a single (most specific) definition, it will be taken as + * actual argument for the implicit parameter. Otherwise it's an error. + * + * = Type Classes = + * + * The combination of types parameterized and implicit parameters is also + * called ''type classes''. + * + * = Exercises = + * + * Define an ordering for the `Rational` type: + * + * {{{ + * class Rational(x: Int, y: Int) { + * + * private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) + * private val g = gcd(x, y) + * + * lazy val numer: Int = x / g + * lazy val denom: Int = y / g + * } + * }}} + */ + def rationalOrdering(res0: (Rational, Rational) => Int): Unit = { + /** + * Returns an integer whose sign communicates how the first parameter + * compares to the second parameter. + * + * The result sign has the following meaning: + * - Negative if the first parameter is less than the second parameter + * - Positive if the first parameter is greater than the second parameter + * - Zero otherwise + */ + val compareRationals: (Rational, Rational) => Int = res0 + + implicit val rationalOrder: Ordering[Rational] = + new Ordering[Rational] { + def compare(x: Rational, y: Rational): Int = compareRationals(x, y) + } + + val half = new Rational(1, 2) + val third = new Rational(1, 3) + val fourth = new Rational(1, 4) + val rationals = List(third, half, fourth) + insertionSort(rationals) shouldBe List(fourth, third, half) + } + } diff --git a/src/test/scala/scalatutorial/sections/TypeClassesSpec.scala b/src/test/scala/scalatutorial/sections/TypeClassesSpec.scala new file mode 100644 index 00000000..d5a6a84b --- /dev/null +++ b/src/test/scala/scalatutorial/sections/TypeClassesSpec.scala @@ -0,0 +1,19 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +import scalatutorial.aux.Rational + +class TypeClassesSpec extends Spec with Checkers { + + def `check rational ordering`: Unit = { + val ordering = + (x: Rational, y: Rational) => x.numer * y.denom - y.numer * x.denom + check(Test.testSuccess(TypeClasses.rationalOrdering _, ordering :: HNil)) + } + +} From c0b588549cd69b212079c4ce3a4a327b95542acc Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 25 Nov 2016 15:59:12 +0100 Subject: [PATCH 17/19] Add more exercises --- .../sections/DefinitionsAndEvaluation.scala | 14 +++++++- .../sections/ImperativeProgramming.scala | 25 ++++++++++++-- .../sections/LexicalScopes.scala | 18 +++++++++- .../sections/ObjectOrientedProgramming.scala | 33 +++++++++++++++++-- .../sections/PolymorphicTypes.scala | 16 ++++++++- .../sections/StandardLibrary.scala | 19 ++++++++--- .../sections/StructuringInformation.scala | 27 ++++++++++++++- .../sections/TermsAndTypes.scala | 22 +++++++++---- .../DefinitionsAndEvaluationSpec.scala | 4 +++ .../sections/HigherOrderFunctionsSpec.scala | 6 ++-- .../sections/ImperativeProgrammingSpec.scala | 5 +++ .../sections/LexicalScopesSpec.scala | 4 +++ .../ObjectOrientedProgrammingSpec.scala | 3 ++ .../sections/PolymorphicTypesSpec.scala | 15 +++++++++ .../sections/StandardLibrarySpec.scala | 4 +++ .../sections/StructuringInformationSpec.scala | 4 ++- .../sections/TailRecursionSpec.scala | 6 ++-- .../sections/TermsAndTypesSpec.scala | 4 +++ 18 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 src/test/scala/scalatutorial/sections/PolymorphicTypesSpec.scala diff --git a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala index 34ea0856..abcf3c71 100644 --- a/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala +++ b/src/main/scala/scalatutorial/sections/DefinitionsAndEvaluation.scala @@ -204,7 +204,19 @@ object DefinitionsAndEvaluation extends ScalaTutorialSection { * corresponding parameter is unused in the evaluation of the function body. * * Scala normally uses call-by-value. + * + * = Exercise = + * + * Complete the following definition of the `triangleArea` function, + * which takes a triangle base and height as parameters and returns + * its area: */ - def nothing(): Unit = () + def triangleAreaExercise(res0: Double, res1: Double): Unit = { + def triangleArea(base: Double, height: Double): Double = + base * height / res0 + + triangleArea(3, 4) shouldBe 6 + triangleArea(5, 6) shouldBe res1 + } } diff --git a/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala index b839fb20..019c901a 100644 --- a/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala +++ b/src/main/scala/scalatutorial/sections/ImperativeProgramming.scala @@ -320,14 +320,33 @@ object ImperativeProgramming extends ScalaTutorialSection { * Example: * * {{{ - * for (i <- 1 until 3; j <- "abc") println(i + " " + j) + * for (i <- 1 until 3; j <- "abc") println(s"$i $j") * }}} * * translates to: * * {{{ - * (1 until 3) foreach (i => "abc" foreach (j => println(i + " " + j))) + * (1 until 3) foreach (i => "abc" foreach (j => println(s"$i $j"))) * }}} + * + * = Exercise = + * + * Complete the following imperative implementation of `factorial`: */ - def nothing(): Unit = () + def factorialExercise(res0: Int, res1: Int, res2: Int): Unit = { + def factorial(n: Int): Int = { + var result = res0 + var i = res1 + while (i <= n) { + result = result * i + i = i + res2 + } + result + } + + factorial(2) shouldBe 2 + factorial(3) shouldBe 6 + factorial(4) shouldBe 24 + factorial(5) shouldBe 120 + } } diff --git a/src/main/scala/scalatutorial/sections/LexicalScopes.scala b/src/main/scala/scalatutorial/sections/LexicalScopes.scala index 710221a6..ecd9c1c0 100644 --- a/src/main/scala/scalatutorial/sections/LexicalScopes.scala +++ b/src/main/scala/scalatutorial/sections/LexicalScopes.scala @@ -267,6 +267,22 @@ object LexicalScopes extends ScalaTutorialSection { * {{{ * $ scala Hello * }}} + * + * = Exercise = + * */ - def nothing(): Unit = () + def objectScopes(res0: Int): Unit = { + object Foo { + val x = 1 + } + object Bar { + val x = 2 + } + object Baz { + import Bar.x + val y = x + Foo.x + } + + Baz.y shouldBe res0 + } } diff --git a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala index 4296433c..28d9925f 100644 --- a/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala +++ b/src/main/scala/scalatutorial/sections/ObjectOrientedProgramming.scala @@ -304,7 +304,7 @@ object ObjectOrientedProgramming extends ScalaTutorialSection { * a computation model based on substitution. Now we extend this * model to classes and objects. * - * How is an instantiation of the class `new C(e1, …, en)` evaluted? + * How is an instantiation of the class `new C(e1, …, en)` evaluated? * * The expression arguments `e1, …, en` * are evaluated like the arguments of a normal function. That's it. @@ -627,6 +627,35 @@ object ObjectOrientedProgramming extends ScalaTutorialSection { * val y: String = null // y: String * val z: Int = null // error: type mismatch * }}} + * + * = Exercise = + * + * The following `Reducer` abstract class defines how to + * reduce a list of values into a single value by starting + * with an initial value and combining it with each element + * of the list: */ - def nothing(): Unit = () + def reducer(res0: Int, res1: Int): Unit = { + abstract class Reducer(init: Int) { + def combine(x: Int, y: Int): Int + def reduce(xs: List[Int]): Int = + xs match { + case Nil => init + case y :: ys => combine(y, reduce(ys)) + } + } + + object Product extends Reducer(1) { + def combine(x: Int, y: Int): Int = x * y + } + + object Sum extends Reducer(0) { + def combine(x: Int, y: Int): Int = x + y + } + + val nums = List(1, 2, 3, 4) + + Product.reduce(nums) shouldBe res0 + Sum.reduce(nums) shouldBe res1 + } } \ No newline at end of file diff --git a/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala index a08b8859..3493afb2 100644 --- a/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala +++ b/src/main/scala/scalatutorial/sections/PolymorphicTypes.scala @@ -419,7 +419,21 @@ object PolymorphicTypes extends ScalaTutorialSection { * * - covariant type parameters may appear in lower bounds of method type parameters * - contravariant type parameters may appear in upper bounds of method + * + * = Exercise = + * + * Complete the following implementation of the `size` function that returns + * the size of a given list. */ - def nothing(): Unit = () + def sizeExercise(res0: Int, res1: Int): Unit = { + def size[A](xs: List[A]): Int = + xs match { + case Nil => res0 + case y :: ys => res1 + size(ys) + } + size(Nil) shouldBe 0 + size(List(1, 2)) shouldBe 2 + size(List("a", "b", "c")) shouldBe 3 + } } diff --git a/src/main/scala/scalatutorial/sections/StandardLibrary.scala b/src/main/scala/scalatutorial/sections/StandardLibrary.scala index a597effd..6fc3b357 100644 --- a/src/main/scala/scalatutorial/sections/StandardLibrary.scala +++ b/src/main/scala/scalatutorial/sections/StandardLibrary.scala @@ -261,21 +261,32 @@ object StandardLibrary extends ScalaTutorialSection { * * === Manipulating `Either[A, B]` Values === * - * `Either` has `map` and `flatMap`. These methods transform the `Right` - * case only. Way say that `Either` is “right biased”: + * Since Scala 2.12, `Either` has `map` and `flatMap`. These methods + * transform the `Right` case only. Way say that `Either` is “right biased”: * * {{{ * Right(1).map((x: Int) => x + 1) shouldBe Right(2) * Left("foo").map((x: Int) => x + 1) shouldBe Left("foo") * }}} * - * `Either` also has a `filterOrElse` method that turn a `Right` value + * `Either` also has a `filterOrElse` method that turns a `Right` value * into a `Left` value if it does not satisfy a given predicate: * * {{{ * Right(1).filterOrElse(x => x % 2 == 0, "Odd value") shouldBe Left("Odd value") * }}} + * + * However, prior to Scala 2.12, `Either` was “unbiased”. You had to explicitly + * specify which “side” (`Left` or `Right`) you wanted to `map`: */ - def nothing(): Unit = () + def either(res0: Either[String, Int], res1: Either[String, Int]): Unit = { + def triple(x: Int): Int = 3 * x + + def tripleEither(x: Either[String, Int]): Either[String, Int] = + x.right.map(triple) + + tripleEither(Right(1)) shouldBe res0 + tripleEither(Left("not a number")) shouldBe res1 + } } diff --git a/src/main/scala/scalatutorial/sections/StructuringInformation.scala b/src/main/scala/scalatutorial/sections/StructuringInformation.scala index d8c31924..1055e970 100644 --- a/src/main/scala/scalatutorial/sections/StructuringInformation.scala +++ b/src/main/scala/scalatutorial/sections/StructuringInformation.scala @@ -42,6 +42,9 @@ object StructuringInformation extends ScalaTutorialSection { * val c3 = Note("C", "Quarter", 3) * }}} * + * `c3` is a value that aggregates the arguments passed to the `Note` + * constructor. + * * Then, you can retrieve the information carried by each ''member'' (`name`, * `duration` and `octave`) by using the dot notation: */ @@ -221,6 +224,28 @@ object StructuringInformation extends ScalaTutorialSection { * {{{ * case class Note(name: String, duration: String, octave: Int) extends Symbol * }}} + * + * = Exercise = + * + * Consider the following algebraic data type that models note durations. + * Complete the implementation of the function `fractionOfWhole`, which + * takes as parameter a duration and returns the corresponding fraction + * of the `Whole` duration. */ - def nothing(): Unit = () + def adts(res0: Double, res1: Double): Unit = { + sealed trait Duration + case object Whole extends Duration + case object Half extends Duration + case object Quarter extends Duration + + def fractionOfWhole(duration: Duration): Double = + duration match { + case Whole => 1.0 + case Half => res0 + case Quarter => res1 + } + + fractionOfWhole(Half) shouldBe 0.5 + fractionOfWhole(Quarter) shouldBe 0.25 + } } diff --git a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala index 4751feb2..8b42d1d1 100644 --- a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala +++ b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala @@ -164,13 +164,23 @@ object TermsAndTypes extends ScalaTutorialSection { /** * = Common Types = * - * - `Int`: 32-bit integers - * - `Double`: 64-bit floating point numbers - * - `Boolean`: boolean values - * - `String`: text + * - `Int`: 32-bit integers (e.g. `1`, `23`, `456`) + * - `Double`: 64-bit floating point numbers (e.g. `1.0`, `2.3`, `4.56`) + * - `Boolean`: boolean values (`true` and `false`) + * - `String`: text (e.g. `"foo"`, `"bar"`) * - * Note that type names always begin with an upper case. + * Note that type names always begin with an upper case letter. + * + * = Exercise = + * + * Here are some more methods of standard types. Can you guess what they do? */ - def nothing(): Unit = () + def moreMethods(res0: String, res1: Boolean, res2: String): Unit = { + 16.toHexString shouldBe res0 + (0 to 10).contains(10) shouldBe true + (0 until 10).contains(10) shouldBe res1 + "foo".drop(1) shouldBe "oo" + "bar".take(2) shouldBe res2 + } } diff --git a/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala b/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala index e218dd96..56bb8b03 100644 --- a/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala +++ b/src/test/scala/scalatutorial/sections/DefinitionsAndEvaluationSpec.scala @@ -16,4 +16,8 @@ class DefinitionsAndEvaluationSpec extends Spec with Checkers { check(Test.testSuccess(DefinitionsAndEvaluation.areaExercise _, 314.159 :: HNil)) } + def `check triangle area`: Unit = { + check(Test.testSuccess(DefinitionsAndEvaluation.triangleAreaExercise _, 2.0 :: 15.0 :: HNil)) + } + } diff --git a/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala index ff3f0948..094f1436 100644 --- a/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala +++ b/src/test/scala/scalatutorial/sections/HigherOrderFunctionsSpec.scala @@ -8,8 +8,8 @@ import shapeless.HNil class HigherOrderFunctionsSpec extends Spec with Checkers { - def `check tail rec sum`: Unit = { - check(Test.testSuccess(HigherOrderFunctions.tailRecSum _, 1 :: 0 :: HNil)) - } +// def `check tail rec sum`: Unit = { +// check(Test.testSuccess(HigherOrderFunctions.tailRecSum _, 1 :: 0 :: HNil)) +// } } diff --git a/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala b/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala index ee91f23a..9b35b656 100644 --- a/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala +++ b/src/test/scala/scalatutorial/sections/ImperativeProgrammingSpec.scala @@ -12,4 +12,9 @@ class ImperativeProgrammingSpec extends Spec with Checkers { check(Test.testSuccess(ImperativeProgramming.observationalEquivalence _, 10 :: HNil)) } +// Disabled because property based testing generates numbers that are too expensive too compute +// def `check factorial`: Unit = { +// check(Test.testSuccess(ImperativeProgramming.factorialExercise _, 1 :: 2 :: 1 :: HNil)) +// } + } diff --git a/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala b/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala index b1f1bcb6..92dfdb49 100644 --- a/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala +++ b/src/test/scala/scalatutorial/sections/LexicalScopesSpec.scala @@ -12,4 +12,8 @@ class LexicalScopesSpec extends Spec with Checkers { check(Test.testSuccess(LexicalScopes.scopeRules _, 16 :: HNil)) } + def `check objects scopes`: Unit = { + check(Test.testSuccess(LexicalScopes.objectScopes _, 3 :: HNil)) + } + } diff --git a/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala b/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala index 5067eb86..dfd927c8 100644 --- a/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala +++ b/src/test/scala/scalatutorial/sections/ObjectOrientedProgrammingSpec.scala @@ -12,6 +12,9 @@ class ObjectOrientedProgrammingSpec extends Spec with Checkers { check(Test.testSuccess(ObjectOrientedProgramming.dynamicBinding _, false :: true :: HNil)) } + def `check reducer`: Unit = { + check(Test.testSuccess(ObjectOrientedProgramming.reducer _, 24 :: 10 :: HNil)) + } } diff --git a/src/test/scala/scalatutorial/sections/PolymorphicTypesSpec.scala b/src/test/scala/scalatutorial/sections/PolymorphicTypesSpec.scala new file mode 100644 index 00000000..a9394d27 --- /dev/null +++ b/src/test/scala/scalatutorial/sections/PolymorphicTypesSpec.scala @@ -0,0 +1,15 @@ +package scalatutorial.sections + +import org.scalacheck.Shapeless._ +import org.scalaexercises.Test +import org.scalatest.Spec +import org.scalatest.prop.Checkers +import shapeless.HNil + +class PolymorphicTypesSpec extends Spec with Checkers { + + def `check size`: Unit = { + check(Test.testSuccess(PolymorphicTypes.sizeExercise _, 0 :: 1 :: HNil)) + } + +} diff --git a/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala b/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala index 44a75e1f..576523f4 100644 --- a/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala +++ b/src/test/scala/scalatutorial/sections/StandardLibrarySpec.scala @@ -12,4 +12,8 @@ class StandardLibrarySpec extends Spec with Checkers { check(Test.testSuccess(StandardLibrary.insertionSort _, ((_: Int) < (_: Int)) :: List.empty[Int] :: HNil)) } + def `check either`: Unit = { + check(Test.testSuccess(StandardLibrary.either _, (Right[String, Int](3): Either[String, Int]) :: (Left[String, Int]("not a number"): Either[String, Int]) :: HNil)) + } + } diff --git a/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala b/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala index 4356b4af..0be59fc4 100644 --- a/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala +++ b/src/test/scala/scalatutorial/sections/StructuringInformationSpec.scala @@ -16,6 +16,8 @@ class StructuringInformationSpec extends Spec with Checkers { check(Test.testSuccess(StructuringInformation.caseClassEquals _, true :: false :: HNil)) } - + def `check adts`: Unit = { + check(Test.testSuccess(StructuringInformation.adts _, 0.5 :: 0.25 :: HNil)) + } } diff --git a/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala b/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala index d2b1c7ab..38d35f48 100644 --- a/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala +++ b/src/test/scala/scalatutorial/sections/TailRecursionSpec.scala @@ -8,8 +8,8 @@ import shapeless.HNil class TailRecursionSpec extends Spec with Checkers { - def `check tail recursive factorial`: Unit = { - check(Test.testSuccess(TailRecursion.tailRecFactorial _, 0 :: 1 :: 1 :: HNil)) - } +// def `check tail recursive factorial`: Unit = { +// check(Test.testSuccess(TailRecursion.tailRecFactorial _, 0 :: 1 :: 1 :: HNil)) +// } } diff --git a/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala b/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala index 55eece22..03a4a0ca 100644 --- a/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala +++ b/src/test/scala/scalatutorial/sections/TermsAndTypesSpec.scala @@ -20,4 +20,8 @@ class TermsAndTypesSpec extends Spec with Checkers { // check(Test.testSuccess(TermsAndTypes.staticTyping _, 10 :: HNil)) // } + def `check more methods`: Unit = { + check(Test.testSuccess(TermsAndTypes.moreMethods _, "10" :: false :: "ba" :: HNil)) + } + } From d933072e878dbafe7c8837abef3ed8fa9d6ef015 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 25 Nov 2016 16:00:46 +0100 Subject: [PATCH 18/19] Tweaks --- src/main/scala/scalatutorial/ScalaTutorial.scala | 2 +- src/main/scala/scalatutorial/sections/TermsAndTypes.scala | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/scala/scalatutorial/ScalaTutorial.scala b/src/main/scala/scalatutorial/ScalaTutorial.scala index 5d8c2451..74c90960 100644 --- a/src/main/scala/scalatutorial/ScalaTutorial.scala +++ b/src/main/scala/scalatutorial/ScalaTutorial.scala @@ -10,7 +10,7 @@ import sections._ */ object ScalaTutorial extends Library { val owner = "scala-exercises" - val repository = "exercises-fpprinciples" + val repository = "exercises-scalatutorial" override val color = Some("#f26527") val logoPath = "scala-tutorial" diff --git a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala index 8b42d1d1..8cc98135 100644 --- a/src/main/scala/scalatutorial/sections/TermsAndTypes.scala +++ b/src/main/scala/scalatutorial/sections/TermsAndTypes.scala @@ -11,6 +11,9 @@ object TermsAndTypes extends ScalaTutorialSection { * The contents is based on the MOOCS [[https://www.coursera.org/learn/progfun1/home Functional Programming Principles in Scala]] * and [[https://www.coursera.org/learn/progfun2/home Functional Program Design in Scala]]. * + * The target audience is people who already have ''some'' experience of programming and who are familiar with + * the JVM. + * * = Elements of Programming = * * Programming languages give programmers ways to express computations. From 84218988d6e23938c3b5fc017679ccb70852ff2b Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 18 Jan 2017 14:38:44 +0100 Subject: [PATCH 19/19] Update paths in README according to the original github repository --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb6f8d81..822e65ab 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ This repository hosts a library for the first course of the [Scala MOOC](https:/ - Clone this repository, compile and publish the project: ~~~ sh -git clone git@github.com:scalacenter/exercises-fpprinciples.git -cd exercises-fpprinciples/ +git clone git@github.com:scala-exercises/exercises-scalatutorial.git +cd exercises-scalatutorial/ sbt compile publishLocal # it is important to first run the `compile` command alone ~~~