diff --git a/doc/sjs-for-js/es6-to-scala-part3.md b/doc/sjs-for-js/es6-to-scala-part3.md index f764b851..9d920d94 100644 --- a/doc/sjs-for-js/es6-to-scala-part3.md +++ b/doc/sjs-for-js/es6-to-scala-part3.md @@ -10,7 +10,7 @@ more useful design patterns and features, to get you started quickly. ## Pattern matching -In the Basics part we already saw simple examples of _pattern matching_ as a replacement for JavaScript's `switch` +In the Basics part we already saw simple examples of *pattern matching* as a replacement for JavaScript's `switch` statement. However, it can be used for much more, for example checking the type of input. {% columns %} @@ -51,8 +51,8 @@ def printType(o: Any): Unit = { {% endcolumn %} {% endcolumns %} -Pattern matching uses something called _partial functions_ which means it can be used in place of regular functions, for -example in a call to `filter` or `map`. You can also add a _guard clause_ in the form of an `if`, to limit the match. If +Pattern matching uses something called *partial functions* which means it can be used in place of regular functions, for +example in a call to `filter` or `map`. You can also add a *guard clause* in the form of an `if`, to limit the match. If you need to match to a variable, use backticks to indicate that. {% columns %} @@ -260,10 +260,224 @@ Here we use triple-quoted strings that allow us to write regex without escaping converted into a {% scaladoc util.matching.Regex %} object with the `.r` method. Because regexes extract strings, we need to convert matched groups to integers ourselves. +## Functions revisited + +We covered the basic use functions in [Part 1](es6-to-scala-part1.html), but Scala, being a functional programming +language, provides much more when it comes to functions. Let's explore some of the more advanced features and how they +compare to JavaScript. + +#### Higher-order functions + +Scala, as JavaScript, allows the definition of higher-order functions. These are functions that take other functions as +parameters, or whose result is a function. Higher-order functions should be familiar to JavaScript developers, because +they often appear in form of functions that take callbacks as parameters. + +Typically higher-order functions are used to pass specific functionality to a general function, like in the case of +`Array.prototype.filter` in ES6 or `Seq.filter` in Scala. We can use this to build a function to calculate a minimum and +maximum from a sequence of values, using a function to extract the target value. + +{% columns %} +{% column 6 ES6 %} +{% highlight javascript %} +function minmaxBy(arr, f) { + return arr.reduce( + ([min, max], e) => { + const v = f(e); + return [Math.min(min, v), Math.max(max, v)] + }, + [Number.MAX_VALUE, Number.MIN_VALUE] + ) +} +const [youngest, oldest] = minmaxBy(persons, e => e.age); +{% endhighlight %} +{% endcolumn %} + +{% column 6 Scala %} +{% highlight scala %} +def minmaxBy[T](seq: Seq[T], f: T => Int): (Int, Int) = { + seq.foldLeft((Int.MaxValue, Int.MinValue)) { + case ((min, max), e) => + val v = f(e) + (math.min(min, v), math.max(max, v)) + } +} +val (youngest, oldest) = minmaxBy[Person](persons, _.age) +{% endhighlight %} +{% endcolumn %} +{% endcolumns %} + +#### Call-by-Name + +In some cases you want to *defer* the evaluation of a parameter value until when it's actually used in the function. For +this purpose Scala offers *call-by-name* parameters. This can be useful when dealing with an expensive computation that +is only optionally used by the function. In JavaScript the closest thing to this is to wrap a value in an anonymous +function with no arguments and pass *that* as a parameter, but that's more verbose and error-prone. You need to remember +to both wrap the value and call the function. + +{% columns %} +{% column 6 ES6 %} +{% highlight javascript %} +function compute(value, cPos, cNeg) { + if (value >= 0) + return cPos(); + else + return cNeg(); +} + +compute(x, () => expCalc(), () => expCalc2()); +{% endhighlight %} +{% endcolumn %} + +{% column 6 Scala %} +{% highlight scala %} +def compute(value: Int, cPos: => Int, cNeg: => Int) = { + if (value >= 0) + cPos + else + cNeg +} + +compute(x, expCalc, expCalc2) +{% endhighlight %} +{% endcolumn %} +{% endcolumns %} + +#### Recursive functions + +Recursive functions can be very expressive, but they may also cause spurious stack overflows if the recursion gets too +deep. Scala automatically optimizes recursive functions that are *tail recursive*, allowing you to use them without +fear of overflowing the stack. To make sure your function is actually tail recursive, use the `@tailrec` annotation, +which will cause the Scala compiler to report an error if your function is not tail recursive. + +Before ES6, JavaScript did not support tail call optimization, nor optimizing tail recursive functions. If you use a +smart ES6 transpiler, it can actually convert a tail recursive function into a `while` loop, but there are no checks +available to help you to verify the validity of tail recursion. + +{% columns %} +{% column 6 ES6 %} +{% highlight javascript %} +function fib(n) { + function fibIter(n, next, prev) { + if (n === 0) { + return prev; + } else { + return fibIter(n - 1, next + prev, next); + } + }; + return fibIter(n, 1, 0); +} +{% endhighlight %} +{% endcolumn %} + +{% column 6 Scala %} +{% highlight scala %} +def fib(n: Int): Int = { + @tailrec + def fibIter(n: Int, next: Int, prev: Int): Int = { + if (n == 0) + prev + else + fibIter(n - 1, next + prev, next) + } + fibIter(n, 1, 0) +} +{% endhighlight %} +{% endcolumn %} +{% endcolumns %} + + +#### Partially applied functions + +In Scala you can call a function with only some of its arguments and get back a function taking those missing arguments. +You do this by using `_` in place of the actual parameter. In JavaScript you can achieve the same by using the +`Function.prototype.bind` function (although it limits you to providing parameters from left to right). For example we +can define a function to create HTML tags by wrapping content within start and end tags. + +{% columns %} +{% column 6 ES6 %} +{% highlight javascript %} +function tag(name, content) { + return `<${name}>${content}${name}>` +} + +const div = tag.bind(null, "div"); +const p = tag.bind(null, "p"); +const html = div(p("test")); //
test
test
test
test