Skip to content

Commit

Permalink
fp in scala: getting started
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathon Belotti committed Jul 19, 2021
1 parent 26f0733 commit 55dd853
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 0 deletions.
1 change: 1 addition & 0 deletions .ijwb/.bazelproject
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ additional_languages:
python
# scala
# typescript
scala
23 changes: 23 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,29 @@ register_toolchains("//tools/build/bazel/py_toolchain:py_toolchain")
# Scala
###########

# Avoid compiling protobuf.
# See https://github.com/bazelbuild/rules_scala/issues/1254#issuecomment-882522899
# for why this is being done.
http_archive(
name = "rules_proto",
sha256 = "8e7d59a5b12b233be5652e3d29f42fba01c7cbab09f6b3a8d0a57ed6d1e9a0da",
strip_prefix = "rules_proto-7e4afce6fe62dbff0a4a03450143146f9f2d7488",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/7e4afce6fe62dbff0a4a03450143146f9f2d7488.tar.gz",
"https://github.com/bazelbuild/rules_proto/archive/7e4afce6fe62dbff0a4a03450143146f9f2d7488.tar.gz",
],
)

load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")

# Declares @com_google_protobuf//:protoc pointing to released binary
# This should stop building protoc during bazel build
# See https://github.com/bazelbuild/rules_proto/pull/36
rules_proto_dependencies()

rules_proto_toolchains()


skylib_version = "1.0.3"
http_archive(
name = "bazel_skylib",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library", "scala_binary", "scala_repl")

scala_library(
name = "gettingstarted",
srcs = ["GettingStarted.scala"]
)

scala_binary(
name = "MyModule",
main_class = "fpinscala.gettingstarted.MyModule",
srcs = ["GettingStarted.scala"],
)

scala_binary(
name = "FormatAbsAndFactorial",
main_class = "fpinscala.gettingstarted.FormatAbsAndFactorial",
srcs = ["GettingStarted.scala"],
)

# Exercise 2.1
scala_binary(
name = "TestFib",
main_class = "fpinscala.gettingstarted.TestFib",
srcs = ["GettingStarted.scala"],
)


scala_repl(
name = "repl",
deps = [
":gettingstarted",
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package fpinscala.gettingstarted

// A comment! Yay
/* Another comment */
/** A documentation comment */
object MyModule {
def abs(n: Int): Int =
if (n < 0) -n
else n

private def formatAbs(x: Int) = {
val msg = "The absolute value of %d is %d"
msg.format(x, abs(x))
}

def main(args: Array[String]): Unit =
println(formatAbs(-42))

// A definition of factorial, using a local, tail recursive function
def factorial(n: Int): Int = {
@annotation.tailrec
def go(n: Int, acc: Int): Int =
if (n <= 0) acc
else go(n-1, n*acc)

go(n, 1)
}

// Another implementation of `factorial`, this time with a `while` loop
def factorial2(n: Int): Int = {
var acc = 1
var i = n
while (i > 0) { acc *= i; i -= 1 }
acc
}

// Exercise 2.1: Write a function to get the nth Fibonacci number
// The nth number is always the sum of the previous two.
def fib(n: Int): Int = {
@annotation.tailrec
def go(n: Int, previous: Int, current: Int): Int = {
if (n <= 0) previous
else go(n - 1, current, previous + current)
}
go(n, 0, 1)
}

// This definition and `formatAbs` are very similar..
private def formatFactorial(n: Int) = {
val msg = "The factorial of %d is %d."
msg.format(n, factorial(n))
}

// We can generalize `formatAbs` and `formatFactorial` to
// accept a _function_ as a parameter
def formatResult(name: String, n: Int, f: Int => Int) = {
val msg = "The %s of %d is %d."
msg.format(name, n, f(n))
}
}

object FormatAbsAndFactorial {

import MyModule._

// Now we can use our general `formatResult` function
// with both `abs` and `factorial`
def main(args: Array[String]): Unit = {
println(formatResult("absolute value", -42, abs))
println(formatResult("factorial", 7, factorial))
}
}

object TestFib {

import MyModule._

// test implementation of `fib`
def main(args: Array[String]): Unit = {
println("Expected: 0, 1, 1, 2, 3, 5, 8")
println("Actual: %d, %d, %d, %d, %d, %d, %d".format(fib(0), fib(1), fib(2), fib(3), fib(4), fib(5), fib(6)))
}
}

// Functions get passed around so often in FP that it's
// convenient to have syntax for constructing a function
// *without* having to give it a name
object AnonymousFunctions {
import MyModule._

// Some examples of anonymous functions:
def main(args: Array[String]): Unit = {
println(formatResult("absolute value", -42, abs))
println(formatResult("factorial", 7, factorial))
println(formatResult("increment", 7, (x: Int) => x + 1))
println(formatResult("increment2", 7, (x) => x + 1))
println(formatResult("increment3", 7, x => x + 1))
println(formatResult("increment4", 7, _ + 1))
println(formatResult("increment5", 7, x => { val r = x + 1; r }))
}
}

object MonomorphicBinarySearch {

// First, a binary search implementation, specialized to `Double`,
// another primitive type in Scala, representing 64-bit floating
// point numbers
// Ideally, we could generalize this to work for any `Array` type,
// so long as we have some way of comparing elements of the `Array`
def binarySearch(ds: Array[Double], key: Double): Int = {
@annotation.tailrec
def go(low: Int, mid: Int, high: Int): Int = {
if (low > high) -mid - 1
else {
val mid2 = (low + high) / 2
val d = ds(mid2) // We index into an array using the same
// syntax as function application
if (d == key) mid2
else if (d > key) go(low, mid2, mid2-1)
else go(mid2 + 1, mid2, high)
}
}
go(0, 0, ds.length - 1)
}

}

object PolymorphicFunctions {

// Here's a polymorphic version of `binarySearch`, parameterized on
// a function for testing whether an `A` is greater than another `A`.
def binarySearch[A](as: Array[A], key: A, gt: (A,A) => Boolean): Int = {
@annotation.tailrec
def go(low: Int, mid: Int, high: Int): Int = {
if (low > high) -mid - 1
else {
val mid2 = (low + high) / 2
val a = as(mid2)
val greater = gt(a, key)
if (!greater && !gt(key,a)) mid2
else if (greater) go(low, mid2, mid2-1)
else go(mid2 + 1, mid2, high)
}
}
go(0, 0, as.length - 1)
}

// Exercise 2.2: Implement `isSorted`, which checks whether an Array[A]
// is sorted according to a given comparison function:
def isSorted[A](as: Array[A], gt: (A,A) => Boolean): Boolean = {
def go(i: Int, hi: Int): Boolean = {
if (i > hi) true
else if (!gt(as(i), as(i-1))) false
else go(i+1, hi)
}

go(1, as.length - 1)
}

// Polymorphic functions are often so constrained by their type
// that they only have one implementation! Here's an example:

def partial1[A,B,C](a: A, f: (A,B) => C): B => C =
(b: B) => f(a, b)

// Exercise 3: Implement `curry`.

// Note that `=>` associates to the right, so we could
// write the return type as `A => B => C`
def curry[A,B,C](f: (A, B) => C): A => (B => C) =
???

// NB: The `Function2` trait has a `curried` method already

// Exercise 4: Implement `uncurry`
def uncurry[A,B,C](f: A => B => C): (A, B) => C =
???

/*
NB: There is a method on the `Function` object in the standard library,
`Function.uncurried` that you can use for uncurrying.
Note that we can go back and forth between the two forms. We can curry
and uncurry and the two forms are in some sense "the same". In FP jargon,
we say that they are _isomorphic_ ("iso" = same; "morphe" = shape, form),
a term we inherit from category theory.
*/

// Exercise 5: Implement `compose`

def compose[A,B,C](f: B => C, g: A => B): A => C =
???
}
14 changes: 14 additions & 0 deletions books/fp_in_scala/src/main/scala/fpinscala/gettingstarted/repl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

set -euo pipefail

REPO_ROOT="$(git rev-parse --show-toplevel)"

main() {
pushd "$REPO_ROOT"
bazel build //books/fp_in_scala/src/main/scala/fpinscala/gettingstarted:repl
./bazel-bin/books/fp_in_scala/src/main/scala/fpinscala/gettingstarted/repl
popd "$REPO_ROOT"
}

main "$@"

0 comments on commit 55dd853

Please sign in to comment.