├── .gitignore ├── .travis.yml ├── README.md ├── build.sbt ├── project ├── build.properties └── plugins.sbt ├── sbt └── src ├── main └── scala │ ├── challenge │ ├── Lens.scala │ ├── Parser.scala │ └── Zipper.scala │ ├── intro │ ├── CheatSheet.scala │ ├── Equal.scala │ ├── Functor.scala │ ├── Id.scala │ ├── Intro.scala │ ├── List.scala │ ├── Monoid.scala │ ├── Optional.scala │ ├── Result.scala │ └── Scala.scala │ └── patterns │ ├── Applicative.scala │ ├── Http.scala │ ├── HttpT.scala │ ├── HttpValue.scala │ ├── Monad.scala │ ├── MonadTrans.scala │ ├── Reader.scala │ ├── ReaderT.scala │ ├── State.scala │ ├── StateT.scala │ ├── TypeLambda.scala │ ├── Writer.scala │ └── WriterT.scala └── test └── scala └── intro ├── ListSpecification.scala └── MonoidSpecification.scala /.gitignore: -------------------------------------------------------------------------------- 1 | *.iws 2 | *.ipr 3 | out 4 | target 5 | .idea 6 | .idea_modules 7 | .lib 8 | /lib 9 | /dependency 10 | /dist 11 | /tmp 12 | /project/boot 13 | /.project 14 | /.classpath 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | scala: 3 | - 2.11.5 4 | 5 | jdk: 6 | - openjdk6 7 | 8 | script: sbt ++$TRAVIS_SCALA_VERSION "test:compile" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction to FP in Scala [![Build Status](https://travis-ci.org/ambiata/introduction-to-fp-in-scala.svg)](https://travis-ci.org/ambiata/introduction-to-fp-in-scala) 2 | This is the base project for the workshop. 3 | 4 | __note__: please test your environment before you arrive so we can get started quickly on the day. 5 | 6 | 7 | ## Getting started 8 | 9 | Before you attend you will need to get a few things 10 | ready and ensure everything is setup properly. `sbt` 11 | is going to do all the heavy lifting though, so 12 | hopefully it is all straight forward, if not, send 13 | us an email via . 14 | 15 | 16 | Pre-requisites. 17 | 18 | 1. A valid install of java 6+ 19 | 2. git 20 | 3. **if you are windows only** install sbt using the [msi installer](http://scalasbt.artifactoryonline.com/scalasbt/sbt-native-packages/org/scala-sbt/sbt/0.13.0/sbt.msi) 21 | 22 | 23 | Getting scala and validating your environment (for unix): 24 | 25 | git clone https://github.com/markhibberd/introduction-to-fp-in-scala.git 26 | cd introduction-to-fp-in-scala 27 | ./sbt "test:compile" 28 | 29 | 30 | Getting scala and validating your environment (for windows): 31 | 32 | git clone https://github.com/markhibberd/introduction-to-fp-in-scala.git 33 | cd introduction-to-fp-in-scala 34 | sbt "test:compile" 35 | 36 | 37 | For either platform this may take a few minutes. It will: 38 | 39 | 1. Download the sbt build tool. 40 | 2. Download the required versions of scala. 41 | 3. Compile the main and test source code. 42 | 4. Run the tests. 43 | 44 | You should see green output, no errors and, an exit code of 0. 45 | 46 | ## Working with scala. 47 | 48 | Any good text editor will be sufficient for the course. If you 49 | prefer an IDE, you can use the eclipse based scala-ide, 50 | intellij, or emacs with ensime. There are commented out lines 51 | in `project/plugins.sbt` that will help you get started: 52 | 53 | You can generate project files for intellij with (after uncommenting sbt-idea plugin): 54 | 55 | ./sbt 'gen-idea no-classifiers' 56 | 57 | You can generate project files for eclipse with (after uncommenting sbteclipse-plugin plugin): 58 | 59 | ./sbt eclipse 60 | 61 | If you want to use ensime (after uncommenting ensime-sbt-cmd): 62 | 63 | ./sbt 'ensime generate' 64 | 65 | Just note that if you choose eclipse or intellij, have a 66 | backup texteditor as well, because there won't be enough 67 | time to debug any editor issues. 68 | 69 | 70 | ## The Plan 71 | 72 | There is about two weeks worth of material available, people with 73 | different backgrounds will progress through at different rates. 74 | 75 | 76 | ### Just enough scala 77 | 78 | - `src/main/scala/intro/Scala.scala` 79 | - `src/main/scala/intro/CheatSheet.scala` 80 | 81 | 82 | ### What is functional programming? 83 | 84 | - `src/main/scala/intro/Intro.scala` 85 | 86 | 87 | ### Introduction to data structures and higher order functions 88 | 89 | #### Examples 90 | 91 | - `src/main/scala/intro/Id.scala` 92 | - `src/main/scala/intro/Optional.scala` 93 | 94 | #### Exercises 95 | 96 | - Lists - `src/main/scala/intro/List.scala` 97 | - Errors without exceptions - `src/main/scala/intro/Result.scala` 98 | 99 | 100 | ### Intro to Type Classes 101 | 102 | #### Example 103 | 104 | - `src/main/scala/intro/Equal.scala` 105 | 106 | #### Exercises 107 | 108 | - `src/main/scala/intro/Functor.scala` 109 | 110 | 111 | ### Algebra for fun and profit 112 | 113 | - `src/main/scala/intro/Monoid.scala` 114 | 115 | ### Property based testing 116 | 117 | - Lists: `src/test/scala/intro/ListProperties.scala` 118 | - Laws: `src/test/scala/intro/MonoidProperties.scala` 119 | 120 | ### Parsers 121 | 122 | - `src/main/scala/challenge/Parser.scala` 123 | 124 | ### Zippers 125 | 126 | - `src/main/scala/challenge/Zippers.scala` 127 | 128 | ### Lenses 129 | 130 | - `src/main/scala/challenge/Lens.scala` (TBD) 131 | 132 | ### Patterns in Types 133 | 134 | - `src/main/scala/patterns/Reader.scala` 135 | - `src/main/scala/patterns/Writer.scala` 136 | - `src/main/scala/patterns/State.scala` 137 | - `src/main/scala/patterns/Http.scala` 138 | - `src/main/scala/patterns/Applicative.scala` 139 | - `src/main/scala/patterns/Monad.scala` 140 | - `src/main/scala/patterns/ReaderT.scala` 141 | - `src/main/scala/patterns/WriterT.scala` 142 | - `src/main/scala/patterns/StateT.scala` 143 | - `src/main/scala/patterns/MonadTrans.scala` 144 | - `src/main/scala/patterns/HttpT.scala` 145 | 146 | ### Freedom 147 | 148 | - `src/main/scala/challenge/Trampoline.scala` (TBD) 149 | - `src/main/scala/challenge/Free.scala` (TBD) 150 | 151 | ### Stream Processing 152 | 153 | - `src/main/scala/challenge/Streams.scala` (TBD) 154 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "introduction-to-fp-in-scala" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | libraryDependencies ++= Seq( 6 | "org.scalaz" %% "scalaz-core" % "7.1.1" 7 | , "org.scalaz" %% "scalaz-scalacheck-binding" % "7.1.1" % "test" 8 | , "org.scalaz.stream" %% "scalaz-stream" % "0.6a" 9 | , "org.specs2" %% "specs2" % "2.4.5" % "test" 10 | , "org.scalacheck" %% "scalacheck" % "1.12.2" % "test" 11 | ) 12 | 13 | resolvers ++= Seq( 14 | "oss snapshots" at "http://oss.sonatype.org/content/repositories/snapshots" 15 | , "oss releases" at "http://oss.sonatype.org/content/repositories/releases" 16 | , "Scalaz Bintray Repo" at "http://dl.bintray.com/scalaz/releases" 17 | ) 18 | 19 | scalacOptions := Seq( 20 | "-deprecation" 21 | , "-unchecked" 22 | , "-Xfatal-warnings" 23 | , "-Xlint" 24 | , "-feature" 25 | , "-language:_" 26 | ) 27 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | //addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.2") 2 | 3 | //addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0") 4 | 5 | //addSbtPlugin("org.ensime" % "ensime-sbt-cmd" % "0.1.2") 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # A more capable sbt runner, coincidentally also called sbt. 4 | # Author: Paul Phillips 5 | 6 | # todo - make this dynamic 7 | declare -r sbt_release_version="0.13.8" 8 | declare -r sbt_unreleased_version="0.13.9-M1" 9 | declare -r buildProps="project/build.properties" 10 | 11 | declare sbt_jar sbt_dir sbt_create sbt_version 12 | declare scala_version sbt_explicit_version 13 | declare verbose noshare batch trace_level log_level 14 | declare sbt_saved_stty debugUs 15 | 16 | echoerr () { echo >&2 "$@"; } 17 | vlog () { [[ -n "$verbose" ]] && echoerr "$@"; } 18 | 19 | # spaces are possible, e.g. sbt.version = 0.13.0 20 | build_props_sbt () { 21 | [[ -r "$buildProps" ]] && \ 22 | grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }' 23 | } 24 | 25 | update_build_props_sbt () { 26 | local ver="$1" 27 | local old="$(build_props_sbt)" 28 | 29 | [[ -r "$buildProps" ]] && [[ "$ver" != "$old" ]] && { 30 | perl -pi -e "s/^sbt\.version\b.*\$/sbt.version=${ver}/" "$buildProps" 31 | grep -q '^sbt.version[ =]' "$buildProps" || printf "\nsbt.version=%s\n" "$ver" >> "$buildProps" 32 | 33 | vlog "!!!" 34 | vlog "!!! Updated file $buildProps setting sbt.version to: $ver" 35 | vlog "!!! Previous value was: $old" 36 | vlog "!!!" 37 | } 38 | } 39 | 40 | set_sbt_version () { 41 | sbt_version="${sbt_explicit_version:-$(build_props_sbt)}" 42 | [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version 43 | export sbt_version 44 | } 45 | 46 | # restore stty settings (echo in particular) 47 | onSbtRunnerExit() { 48 | [[ -n "$sbt_saved_stty" ]] || return 49 | vlog "" 50 | vlog "restoring stty: $sbt_saved_stty" 51 | stty "$sbt_saved_stty" 52 | unset sbt_saved_stty 53 | } 54 | 55 | # save stty and trap exit, to ensure echo is reenabled if we are interrupted. 56 | trap onSbtRunnerExit EXIT 57 | sbt_saved_stty="$(stty -g 2>/dev/null)" 58 | vlog "Saved stty: $sbt_saved_stty" 59 | 60 | # this seems to cover the bases on OSX, and someone will 61 | # have to tell me about the others. 62 | get_script_path () { 63 | local path="$1" 64 | [[ -L "$path" ]] || { echo "$path" ; return; } 65 | 66 | local target="$(readlink "$path")" 67 | if [[ "${target:0:1}" == "/" ]]; then 68 | echo "$target" 69 | else 70 | echo "${path%/*}/$target" 71 | fi 72 | } 73 | 74 | die() { 75 | echo "Aborting: $@" 76 | exit 1 77 | } 78 | 79 | make_url () { 80 | version="$1" 81 | 82 | case "$version" in 83 | 0.7.*) echo "http://simple-build-tool.googlecode.com/files/sbt-launch-0.7.7.jar" ;; 84 | 0.10.* ) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; 85 | 0.11.[12]) echo "$sbt_launch_repo/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; 86 | *) echo "$sbt_launch_repo/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; 87 | esac 88 | } 89 | 90 | init_default_option_file () { 91 | local overriding_var="${!1}" 92 | local default_file="$2" 93 | if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then 94 | local envvar_file="${BASH_REMATCH[1]}" 95 | if [[ -r "$envvar_file" ]]; then 96 | default_file="$envvar_file" 97 | fi 98 | fi 99 | echo "$default_file" 100 | } 101 | 102 | declare -r cms_opts="-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" 103 | declare -r jit_opts="-XX:ReservedCodeCacheSize=256m -XX:+TieredCompilation" 104 | declare -r default_jvm_opts_common="-Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts" 105 | declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" 106 | declare -r latest_28="2.8.2" 107 | declare -r latest_29="2.9.3" 108 | declare -r latest_210="2.10.5" 109 | declare -r latest_211="2.11.7" 110 | 111 | declare -r script_path="$(get_script_path "$BASH_SOURCE")" 112 | declare -r script_name="${script_path##*/}" 113 | 114 | # some non-read-onlies set with defaults 115 | declare java_cmd="java" 116 | declare sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" 117 | declare jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" 118 | declare sbt_launch_repo="http://repo.typesafe.com/typesafe/ivy-releases" 119 | 120 | # pull -J and -D options to give to java. 121 | declare -a residual_args 122 | declare -a java_args 123 | declare -a scalac_args 124 | declare -a sbt_commands 125 | 126 | # args to jvm/sbt via files or environment variables 127 | declare -a extra_jvm_opts extra_sbt_opts 128 | 129 | addJava () { 130 | vlog "[addJava] arg = '$1'" 131 | java_args+=("$1") 132 | } 133 | addSbt () { 134 | vlog "[addSbt] arg = '$1'" 135 | sbt_commands+=("$1") 136 | } 137 | setThisBuild () { 138 | vlog "[addBuild] args = '$@'" 139 | local key="$1" && shift 140 | addSbt "set $key in ThisBuild := $@" 141 | } 142 | addScalac () { 143 | vlog "[addScalac] arg = '$1'" 144 | scalac_args+=("$1") 145 | } 146 | addResidual () { 147 | vlog "[residual] arg = '$1'" 148 | residual_args+=("$1") 149 | } 150 | addResolver () { 151 | addSbt "set resolvers += $1" 152 | } 153 | addDebugger () { 154 | addJava "-Xdebug" 155 | addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1" 156 | } 157 | setScalaVersion () { 158 | [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' 159 | addSbt "++ $1" 160 | } 161 | setJavaHome () { 162 | java_cmd="$1/bin/java" 163 | setThisBuild javaHome "Some(file(\"$1\"))" 164 | export JAVA_HOME="$1" 165 | export JDK_HOME="$1" 166 | export PATH="$JAVA_HOME/bin:$PATH" 167 | } 168 | setJavaHomeQuietly () { 169 | addSbt warn 170 | setJavaHome "$1" 171 | addSbt info 172 | } 173 | 174 | # if set, use JDK_HOME/JAVA_HOME over java found in path 175 | if [[ -e "$JDK_HOME/lib/tools.jar" ]]; then 176 | setJavaHomeQuietly "$JDK_HOME" 177 | elif [[ -e "$JAVA_HOME/bin/java" ]]; then 178 | setJavaHomeQuietly "$JAVA_HOME" 179 | fi 180 | 181 | # directory to store sbt launchers 182 | declare sbt_launch_dir="$HOME/.sbt/launchers" 183 | [[ -d "$sbt_launch_dir" ]] || mkdir -p "$sbt_launch_dir" 184 | [[ -w "$sbt_launch_dir" ]] || sbt_launch_dir="$(mktemp -d -t sbt_extras_launchers.XXXXXX)" 185 | 186 | java_version () { 187 | local version=$("$java_cmd" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \") 188 | vlog "Detected Java version: $version" 189 | echo "${version:2:1}" 190 | } 191 | 192 | # MaxPermSize critical on pre-8 jvms but incurs noisy warning on 8+ 193 | default_jvm_opts () { 194 | local v="$(java_version)" 195 | if [[ $v -ge 8 ]]; then 196 | echo "$default_jvm_opts_common" 197 | else 198 | echo "-XX:MaxPermSize=384m $default_jvm_opts_common" 199 | fi 200 | } 201 | 202 | build_props_scala () { 203 | if [[ -r "$buildProps" ]]; then 204 | versionLine="$(grep '^build.scala.versions' "$buildProps")" 205 | versionString="${versionLine##build.scala.versions=}" 206 | echo "${versionString%% .*}" 207 | fi 208 | } 209 | 210 | execRunner () { 211 | # print the arguments one to a line, quoting any containing spaces 212 | vlog "# Executing command line:" && { 213 | for arg; do 214 | if [[ -n "$arg" ]]; then 215 | if printf "%s\n" "$arg" | grep -q ' '; then 216 | printf >&2 "\"%s\"\n" "$arg" 217 | else 218 | printf >&2 "%s\n" "$arg" 219 | fi 220 | fi 221 | done 222 | vlog "" 223 | } 224 | 225 | [[ -n "$batch" ]] && exec /dev/null; then 247 | curl --fail --silent --location "$url" --output "$jar" 248 | elif which wget >/dev/null; then 249 | wget --quiet -O "$jar" "$url" 250 | fi 251 | } && [[ -r "$jar" ]] 252 | } 253 | 254 | acquire_sbt_jar () { 255 | sbt_url="$(jar_url "$sbt_version")" 256 | sbt_jar="$(jar_file "$sbt_version")" 257 | 258 | [[ -r "$sbt_jar" ]] || download_url "$sbt_url" "$sbt_jar" 259 | } 260 | 261 | usage () { 262 | cat < display stack traces with a max of frames (default: -1, traces suppressed) 281 | -debug-inc enable debugging log for the incremental compiler 282 | -no-colors disable ANSI color codes 283 | -sbt-create start sbt even if current directory contains no sbt project 284 | -sbt-dir path to global settings/plugins directory (default: ~/.sbt/) 285 | -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11+) 286 | -ivy path to local Ivy repository (default: ~/.ivy2) 287 | -no-share use all local caches; no sharing 288 | -offline put sbt in offline mode 289 | -jvm-debug Turn on JVM debugging, open at the given port. 290 | -batch Disable interactive mode 291 | -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted 292 | 293 | # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version) 294 | -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version 295 | -sbt-version use the specified version of sbt (default: $sbt_release_version) 296 | -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version 297 | -sbt-jar use the specified jar as the sbt launcher 298 | -sbt-launch-dir directory to hold sbt launchers (default: ~/.sbt/launchers) 299 | -sbt-launch-repo repo url for downloading sbt launcher jar (default: $sbt_launch_repo) 300 | 301 | # scala version (default: as chosen by sbt) 302 | -28 use $latest_28 303 | -29 use $latest_29 304 | -210 use $latest_210 305 | -211 use $latest_211 306 | -scala-home use the scala build at the specified directory 307 | -scala-version use the specified version of scala 308 | -binary-version use the specified scala version when searching for dependencies 309 | 310 | # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) 311 | -java-home alternate JAVA_HOME 312 | 313 | # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution 314 | # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found 315 | $(default_jvm_opts) 316 | JVM_OPTS environment variable holding either the jvm args directly, or 317 | the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts') 318 | Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument. 319 | -jvm-opts file containing jvm args (if not given, .jvmopts in project root is used if present) 320 | -Dkey=val pass -Dkey=val directly to the jvm 321 | -J-X pass option -X directly to the jvm (-J is stripped) 322 | 323 | # passing options to sbt, OR to this runner 324 | SBT_OPTS environment variable holding either the sbt args directly, or 325 | the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts') 326 | Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument. 327 | -sbt-opts file containing sbt args (if not given, .sbtopts in project root is used if present) 328 | -S-X add -X to sbt's scalacOptions (-S is stripped) 329 | EOM 330 | } 331 | 332 | process_args () 333 | { 334 | require_arg () { 335 | local type="$1" 336 | local opt="$2" 337 | local arg="$3" 338 | 339 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then 340 | die "$opt requires <$type> argument" 341 | fi 342 | } 343 | while [[ $# -gt 0 ]]; do 344 | case "$1" in 345 | -h|-help) usage; exit 1 ;; 346 | -v) verbose=true && shift ;; 347 | -d) addSbt "--debug" && addSbt debug && shift ;; 348 | -w) addSbt "--warn" && addSbt warn && shift ;; 349 | -q) addSbt "--error" && addSbt error && shift ;; 350 | -x) debugUs=true && shift ;; 351 | -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; 352 | -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; 353 | -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; 354 | -no-share) noshare=true && shift ;; 355 | -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; 356 | -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; 357 | -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; 358 | -offline) addSbt "set offline := true" && shift ;; 359 | -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; 360 | -batch) batch=true && shift ;; 361 | -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; 362 | 363 | -sbt-create) sbt_create=true && shift ;; 364 | -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; 365 | -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;; 366 | -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;; 367 | -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;; 368 | -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;; 369 | -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; 370 | -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; 371 | -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;; 372 | -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "Some(file(\"$2\"))" && shift 2 ;; 373 | -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;; 374 | -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; 375 | -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; 376 | 377 | -D*) addJava "$1" && shift ;; 378 | -J*) addJava "${1:2}" && shift ;; 379 | -S*) addScalac "${1:2}" && shift ;; 380 | -28) setScalaVersion "$latest_28" && shift ;; 381 | -29) setScalaVersion "$latest_29" && shift ;; 382 | -210) setScalaVersion "$latest_210" && shift ;; 383 | -211) setScalaVersion "$latest_211" && shift ;; 384 | 385 | --debug) addSbt debug && addResidual "$1" && shift ;; 386 | --warn) addSbt warn && addResidual "$1" && shift ;; 387 | --error) addSbt error && addResidual "$1" && shift ;; 388 | *) addResidual "$1" && shift ;; 389 | esac 390 | done 391 | } 392 | 393 | # process the direct command line arguments 394 | process_args "$@" 395 | 396 | # skip #-styled comments and blank lines 397 | readConfigFile() { 398 | while read line; do 399 | [[ $line =~ ^# ]] || [[ -z $line ]] || echo "$line" 400 | done < "$1" 401 | } 402 | 403 | # if there are file/environment sbt_opts, process again so we 404 | # can supply args to this runner 405 | if [[ -r "$sbt_opts_file" ]]; then 406 | vlog "Using sbt options defined in file $sbt_opts_file" 407 | while read opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file") 408 | elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then 409 | vlog "Using sbt options defined in variable \$SBT_OPTS" 410 | extra_sbt_opts=( $SBT_OPTS ) 411 | else 412 | vlog "No extra sbt options have been defined" 413 | fi 414 | 415 | [[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}" 416 | 417 | # reset "$@" to the residual args 418 | set -- "${residual_args[@]}" 419 | argumentCount=$# 420 | 421 | # set sbt version 422 | set_sbt_version 423 | 424 | # only exists in 0.12+ 425 | setTraceLevel() { 426 | case "$sbt_version" in 427 | "0.7."* | "0.10."* | "0.11."* ) echoerr "Cannot set trace level in sbt version $sbt_version" ;; 428 | *) setThisBuild traceLevel $trace_level ;; 429 | esac 430 | } 431 | 432 | # set scalacOptions if we were given any -S opts 433 | [[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[@]}\"" 434 | 435 | # Update build.properties on disk to set explicit version - sbt gives us no choice 436 | [[ -n "$sbt_explicit_version" ]] && update_build_props_sbt "$sbt_explicit_version" 437 | vlog "Detected sbt version $sbt_version" 438 | 439 | [[ -n "$scala_version" ]] && vlog "Overriding scala version to $scala_version" 440 | 441 | # no args - alert them there's stuff in here 442 | (( argumentCount > 0 )) || { 443 | vlog "Starting $script_name: invoke with -help for other options" 444 | residual_args=( shell ) 445 | } 446 | 447 | # verify this is an sbt dir or -create was given 448 | [[ -r ./build.sbt || -d ./project || -n "$sbt_create" ]] || { 449 | cat < Result[ParseState[A]]) { 17 | /** 18 | * Return a parser with the function `f` applied to the 19 | * output of that parser. 20 | * 21 | * scala> (Parser.value(5) map (x => x + 7)).run("hello") 22 | * = Ok(ParseState(hello,12)) 23 | * 24 | * scala> (Parser.failed[Int](NotEnoughInput) map (x => x + 7)).run("hello") 25 | * = Fail(NotEnoughInput) 26 | */ 27 | def map[B](f: A => B): Parser[B] = 28 | ??? 29 | 30 | /** 31 | * Return a parser that feeds its input into this parser, and 32 | * 33 | * - if that parser succeeds, apply its result to function f, and 34 | * then run the resultant parser with the updated input 35 | * 36 | * - if that parser fails with an error, return a parser with 37 | * that error 38 | * 39 | * scala> (Parser.value(5) flatMap (x => Parser.value(x + 7))).run("hello") 40 | * = Ok(ParseState(hello,12)) 41 | * 42 | * scala> (Parser.failed[Int](NotEnoughInput) flatMap (x => Parser.value(x + 7))).run("hello") 43 | * = Fail(NotEnoughInput) 44 | */ 45 | def flatMap[B](f: A => Parser[B]): Parser[B] = 46 | ??? 47 | 48 | /** 49 | * Anonymous flatMap. 50 | * 51 | * Return a parser that feeds its input into this parser, and 52 | * 53 | * - if that parser succeeds, run the next parser with the updated input 54 | * 55 | * - if that parser fails with an error, return a parser with that error 56 | * 57 | * scala> (Parser.value(5) >>> Parser.value(7)).run("hello") 58 | * = Ok(ParseState(hello,7)) 59 | * 60 | * scala> (Parser.failed(NotEnoughInput) >>> Parser.value(7)).run("hello") 61 | * = Fail(NotEnoughInput) 62 | */ 63 | def >>>[B](parser: => Parser[B]): Parser[B] = 64 | ??? 65 | 66 | /** 67 | * Choice. 68 | * 69 | * Return a parser that tries the first parser for a successful value. 70 | * 71 | * - if the first parser succeeds then use this parser 72 | * 73 | * - if the second parser succeeds then try the second parser 74 | * 75 | * scala> (Parser.value(5) ||| Parser.value(7)).run("hello") 76 | * = Ok(ParseState(hello,5)) 77 | * 78 | * scala> (Parser.failed[Int](NotEnoughInput) ||| Parser.value(7)).run("hello") 79 | * = Ok(ParseState(hello,7)) 80 | */ 81 | def |||(f: => Parser[A]): Parser[A] = 82 | ??? 83 | } 84 | 85 | object Parser { 86 | /** 87 | * Return a parser that always succeeds with the given value 88 | * and consumes no input. 89 | * 90 | * scala> Parser.value(5).run("hello") 91 | * = Ok(ParseState(hello, 5)) 92 | */ 93 | def value[A](a: A): Parser[A] = 94 | ??? 95 | 96 | /** 97 | * Return a parser that always fails with the given Error. 98 | * 99 | * scala> Parser.failed(NotEnoughInput).run("hello") 100 | * = Fail(NotEnoughInput) 101 | * 102 | */ 103 | def failed[A](error: Error): Parser[A] = 104 | ??? 105 | 106 | /** 107 | * Return a parser that succeeds with a character off the input 108 | * or fails with NotEnoughInput if the input is empty. 109 | * 110 | * scala> Parser.character.run("hello") 111 | * = Ok(ParseState(ello, h)) 112 | * 113 | * scala> Parser.character.run("") 114 | * = Fail(NotEnoughInput) 115 | */ 116 | def character: Parser[Char] = 117 | ??? 118 | 119 | /** 120 | * Return a parser that continues producing a list of values from the 121 | * given parser. 122 | * 123 | * scala> Parser.list(Parser.character).run("hello") 124 | * = Ok(ParseState(,List(h,e,l,l,o))) 125 | * 126 | * scala> Parser.list(Parser.character).run("") 127 | * = Ok(ParseState(,List())) 128 | */ 129 | def list[A](parser: Parser[A]): Parser[List[A]] = 130 | ??? 131 | 132 | /** 133 | * Return a parser that produces at least one value from the 134 | * given parser then continues producing a list of values from 135 | * the given parser (to ultimately produce a non-empty list). 136 | * 137 | * The returned parser fails if the input is empty. 138 | * 139 | * scala> Parser.list1(Parser.character).run("hello") 140 | * = Ok(ParseState(,List(h,e,l,l,o))) 141 | * 142 | * scala> Parser.list1(Parser.character).run("") 143 | * = Fail(NotEnoughInput) 144 | */ 145 | def list1[A](parser: Parser[A]): Parser[List[A]] = 146 | ??? 147 | 148 | /** 149 | * Return a parser that produces a character but fails if 150 | * 151 | * - The input is empty, or 152 | * 153 | * - The character does not satisfy the given predicate 154 | * 155 | * scala> Parser.satisfy(c => c == 'h').run("hello") 156 | * = Ok(ParseState(ello,h)) 157 | */ 158 | def satisfy(pred: Char => Boolean): Parser[Char] = 159 | ??? 160 | 161 | /** 162 | * Return a parser that produces a character but fails if 163 | * 164 | * - The input is empty, or 165 | * 166 | * - The character does not match the given character 167 | * 168 | * scala> Parser.is('h').run("hello") 169 | * = Ok(ParseState(ello,h)) 170 | */ 171 | def is(char: Char): Parser[Char] = 172 | ??? 173 | 174 | /** 175 | * Return a parser that produces a character between '0' and '9' 176 | * but fails if 177 | * 178 | * - The input is empty, or 179 | * 180 | * - The produced character is not a digit 181 | * 182 | * scala> Parser.digit.run("123hello") 183 | * = Ok(ParseState(23hello,1)) 184 | * 185 | * scala> Parser.digit.run("hello") 186 | * = Fail(UnexpectedInput(h)) 187 | */ 188 | def digit: Parser[Char] = 189 | ??? 190 | 191 | /** 192 | * Return a parser that produces zero or a positive integer but fails if 193 | * 194 | * - The input is empty, or 195 | * 196 | * - The input does not produce a value series of digits 197 | * 198 | * scala> Parser.natural.run("123hello") 199 | * = Ok(ParseState(hello, 123)) 200 | * 201 | * scala> Parser.natural.run("hello") 202 | * = Fail(UnexpectedInput(h)) 203 | */ 204 | def natural: Parser[Int] = 205 | ??? 206 | 207 | /** 208 | * Return a parser that produces a space character but fails if 209 | * 210 | * - The input is empty, or 211 | * 212 | * - The produced character is not a space 213 | * 214 | * scala> Parser.space.run(" hello") 215 | * = Ok(ParseState(hello, )) 216 | * 217 | * scala> Parser.space.run("hello") 218 | * = Fail(UnexpectedInput(h)) 219 | */ 220 | def space: Parser[Char] = 221 | ??? 222 | 223 | /** 224 | * Return a parse that produces one of more space characters 225 | * (consuming until the first non-space) but fails if 226 | * 227 | * - The input is empty, or 228 | * 229 | * - The first produced character is not a space 230 | * 231 | * scala> Parser.spaces1.run(" hello") 232 | * = Ok(ParseState(hello, )) 233 | * 234 | * scala> Parser.spaces1.run("hello") 235 | * = Fail(UnexpectedInput(h)) 236 | * 237 | */ 238 | def spaces1: Parser[String] = 239 | ??? 240 | 241 | /** 242 | * Return a parser that produces a lower-case character but fails if 243 | * 244 | * - The input is empty, or 245 | * 246 | * - The first produced character is not lower-case 247 | * 248 | * scala> Parser.lower.run("hello") 249 | * = Ok(ParseState(ello,h)) 250 | * 251 | * scala> Parser.lower.run("Hello") 252 | * = Fail(UnexpectedInput(H)) 253 | */ 254 | def lower: Parser[Char] = 255 | ??? 256 | 257 | /** 258 | * Return a parser that produces an upper-case character but fails if 259 | * 260 | * - The input is empty, or 261 | * 262 | * - The first produced character is not upper-case 263 | * 264 | * scala> Parser.upper.run("Hello") 265 | * = Ok(ParseState(ello,H)) 266 | * 267 | * scala> Parser.upper.run("hello") 268 | * = Fail(UnexpectedInput(h)) 269 | */ 270 | def upper: Parser[Char] = 271 | ??? 272 | 273 | /** 274 | * Return a parser that produces an alpha character but fails if 275 | * 276 | * - The input is empty, or 277 | * 278 | * - The first produced character is not alpha 279 | * 280 | * scala> Parser.alpha.run("hello") 281 | * = Ok(ParseState(ello,h)) 282 | * 283 | * scala> Parser.alpha.run("?hello") 284 | * = Fail(UnexpectedInput(?)) 285 | */ 286 | def alpha: Parser[Char] = 287 | ??? 288 | 289 | /** 290 | * Return a parser that sequences the given list of parsers by 291 | * producing all their results but fails on the first failing 292 | * parser of the list. 293 | * 294 | * scala> Parser.sequence(List(Parser.character, Parser.character, Parser.character)).run("hello") 295 | * = Ok(ParseState(lo,List(h, e, l))) 296 | * 297 | * scala> Parser.sequence(List(Parser.character, (Parser.failed(NotEnoughInput): Parser[Char]), Parser.character)).run("hello") 298 | * = Fail(NotEnoughInput) 299 | */ 300 | def sequence[A](parsers: List[Parser[A]]): Parser[List[A]] = 301 | ??? 302 | 303 | /** 304 | * Return a parser that produces the given number of values of 305 | * the given parser. This fails if the given parser fails in the 306 | * attempt to produce the given number of values. 307 | * 308 | * 309 | * scala> Parser.thisMany(5, Parser.character).run("hello") 310 | * = Ok(ParseState(,List(h, e, l, l, o))) 311 | * 312 | * scala> Parser.thisMany(6, Parser.character).run("hello") 313 | * = Fail(NotEnoughInput) 314 | */ 315 | def thisMany[A](n: Int, parse: Parser[A]): Parser[List[A]] = 316 | ??? 317 | } 318 | 319 | /** 320 | * *Challenge* Parse a naive personel record. 321 | * 322 | * We have a set of personel records with a "special" format. 323 | * 324 | * Produce a person parser for a record. 325 | */ 326 | object PersonParser { 327 | /* 328 | * A data structure representing a person with the following attributes: 329 | * - name: non empty string that starts with a capital letter 330 | * - age: positive integer 331 | * - address: non empty string that starts with a capital letter 332 | * - phone: string of digits, dots or hyphens that must start with a 333 | * digit and end with a hash (#) 334 | */ 335 | case class Person(name: String, age: Int, phone: String, address: Address) 336 | 337 | /* 338 | * A data structure representing an address with the following attributes: 339 | * - number: positive integer 340 | * - street: non empty string 341 | */ 342 | case class Address(number: Int, street: String) 343 | 344 | /** 345 | * Parse a name, which is a non-empty string that starts with a capital letter. 346 | */ 347 | def nameParser: Parser[String] = 348 | ??? 349 | 350 | /** 351 | * Parse a phone number, which is a string of digits, dots or hyphens that 352 | * starts with a digit and ends with a hash (#). 353 | */ 354 | def phoneParser: Parser[String] = 355 | ??? 356 | 357 | /** 358 | * An address is a positive street number and a non empty string for the 359 | * street name. 360 | */ 361 | def addressParser: Parser[Address] = 362 | ??? 363 | 364 | /** 365 | * An person record is the following parts, each seperated by one or more spaces. 366 | * 367 | *
368 | * 369 | * scala> PersonParser.personParser.run("Homer 39 555.123.939# 742 evergreen") 370 | * = Ok(ParseState(,Person(Homer,39,555.123.939#,Address(742,evergreen)))) 371 | */ 372 | def personParser: Parser[Person] = 373 | ??? 374 | 375 | /** 376 | * Parse all records. 377 | * 378 | * Example usage: 379 | * 380 | * PersonParser.parseAll(PersonParser.Data) 381 | * 382 | * Hint: Use Parser.sequence 383 | */ 384 | def parseAll(data: List[String]): Result[List[Person]] = 385 | ??? 386 | 387 | def Data = List( 388 | "Fred 32 123.456-1213# 301 cobblestone" 389 | , "Barney 31 123.456.1214# 303 cobblestone" 390 | , "Homer 39 555.123.939# 742 evergreen" 391 | , "Flanders 39 555.123.939# 744 evergreen" 392 | ) 393 | } 394 | -------------------------------------------------------------------------------- /src/main/scala/challenge/Zipper.scala: -------------------------------------------------------------------------------- 1 | package challenge 2 | 3 | /** 4 | * 5 | * A `Zipper` is a focussed position, with a list of values to the left and to the right. 6 | * 7 | * For example, taking the list [0,1,2,3,4,5,6], the moving focus to the third position, the zipper looks like: 8 | * 9 | * Zipper(lefts = [2,1,0], focus = 3, rights = [4,5,6]) 10 | * 11 | * Supposing then we move left on this zipper: 12 | * 13 | * Zipper(lefts = [1,0], focus = 2, rights = [3,4,5,6]) 14 | * 15 | * Then suppose we add 17 to the focus of this zipper: 16 | * 17 | * Zipper(lefts = [1,0], focus = 19, rights = [3,4,5,6]) 18 | */ 19 | case class Zipper[A](lefts: List[A], focus: A, rights: List[A]) { 20 | /** 21 | Exercise 1 22 | ---------- 23 | Map the given function over a Zipper. 24 | */ 25 | def map[B](f: A => B): Zipper[B] = 26 | ??? 27 | 28 | /* 29 | Exercise 2 30 | ---------- 31 | Return true if the zipper has an element to the right. 32 | */ 33 | def hasRight: Boolean = 34 | ??? 35 | 36 | /* 37 | Exercise 2 38 | ---------- 39 | Return true if the zipper has an element to the left. 40 | */ 41 | def hasLeft: Boolean = 42 | ??? 43 | 44 | /* 45 | Exercise 3 46 | ---------- 47 | Move the zipper one element to the right, or not if there is not a right element. 48 | */ 49 | def right: Option[Zipper[A]] = 50 | ??? 51 | 52 | /* 53 | Exercise 4 54 | ----------- 55 | Move the zipper one element to the left, or not if there is not a left element. 56 | */ 57 | def left: Option[Zipper[A]] = 58 | ??? 59 | 60 | /* 61 | Exercise 5 62 | ----------- 63 | Return the list from this zipper. 64 | 65 | ~~~ Remember to preserve correct ordering 66 | */ 67 | def toList: List[A] = 68 | ??? 69 | 70 | /* 71 | Exercise 6 72 | ----------- 73 | Update the focus with the given function. 74 | */ 75 | def withFocus(k: A => A): Zipper[A] = 76 | ??? 77 | 78 | /* 79 | Exercise 7 80 | ----------- 81 | Set the focus to the given value. 82 | */ 83 | def :=(a: A): Zipper[A] = 84 | ??? 85 | 86 | /* 87 | Exercise 8 88 | ----------- 89 | Move the focus to the right until the focus meets the given predicate. 90 | */ 91 | def findRight(p: A => Boolean): Option[Zipper[A]] = 92 | ??? 93 | 94 | /* 95 | Exercise 9 96 | ----------- 97 | Move the focus to the left until the focus meets the given predicate. 98 | */ 99 | def findLeft(p: A => Boolean): Option[Zipper[A]] = 100 | ??? 101 | 102 | /* 103 | Exercise 10 104 | ----------- 105 | Insert the given value at the focus and push the old focus to the right. 106 | */ 107 | def insertPushRight(a: A): Zipper[A] = 108 | ??? 109 | 110 | /* 111 | Exercise 11 112 | ----------- 113 | Insert the given value at the focus and push the old focus to the left. 114 | */ 115 | def insertPushLeft(a: A): Zipper[A] = 116 | ??? 117 | 118 | /* 119 | Exercise 12 120 | ----------- 121 | Move the focus to the first element. 122 | */ 123 | // @annotation.tailrec 124 | final def start: Zipper[A] = 125 | ??? 126 | 127 | /* 128 | Exercise 13 129 | ----------- 130 | Move the focus to the last element. 131 | */ 132 | // @annotation.tailrec 133 | final def end: Zipper[A] = 134 | ??? 135 | 136 | /* 137 | Exercise 14 138 | ----------- 139 | Swap the focus with the element to the right. If there is no element to the right, leave unchanged. 140 | */ 141 | def swapRight: Zipper[A] = 142 | ??? 143 | 144 | /* 145 | Exercise 15 146 | ----------- 147 | Swap the focus with the element to the left. If there is no element to the left, leave unchanged. 148 | */ 149 | def swapLeft: Zipper[A] = 150 | ??? 151 | 152 | /* 153 | Exercise 16 154 | ----------- 155 | Delete the focus and pull the new focus from the right. If there is no element to the right, leave unchanged. 156 | */ 157 | def deletePullRight: Zipper[A] = 158 | ??? 159 | 160 | /* 161 | Exercise 17 162 | ----------- 163 | Delete the focus and pull the new focus from the left. If there is no element to the left, leave unchanged. 164 | */ 165 | def deletePullLeft: Zipper[A] = 166 | ??? 167 | 168 | /* 169 | Exercise 18 170 | ----------- 171 | Move the focus to the right the given number of times. If the number is negative, move left up to 0 instead. 172 | */ 173 | def rightN(n: Int): Option[Zipper[A]] = 174 | ??? 175 | 176 | /* 177 | Exercise 19 178 | ----------- 179 | Move the focus to the left the given number of times. If the number is negative, move right up to 0 instead. 180 | */ 181 | def leftN(n: Int): Option[Zipper[A]] = 182 | ??? 183 | 184 | /* 185 | Exercise 20 186 | ----------- 187 | Move the focus to the right the given number of times. If the number is negative, move left up to 0 instead. 188 | If the movement exceeds the boundary of the zipper, return the number of times were moved to the boundary (in Left). 189 | */ 190 | def rightAtN(n: Int): Either[Int, Zipper[A]] = 191 | ??? 192 | 193 | /* 194 | Exercise 21 195 | ----------- 196 | Move the focus to the left the given number of times. If the number is negative, move right up to 0 instead. 197 | If the movement exceeds the boundary of the zipper, return the number of times were moved to the boundary (in Left). 198 | */ 199 | def leftAtN(n: Int): Either[Int, Zipper[A]] = 200 | ??? 201 | 202 | /* 203 | Exercise 22 204 | ----------- 205 | Move the focus to the given absolute index in the zipper. 206 | Be careful not to traverse the zipper more than is required. 207 | 208 | ~~~ Use leftAtN to move left 209 | ~~~ Use rightN and leftN 210 | */ 211 | def nth(i: Int): Option[Zipper[A]] = 212 | ??? 213 | } 214 | 215 | object Zipper { 216 | def fromList[A](a: List[A]): Option[Zipper[A]] = 217 | a match { 218 | case Nil => None 219 | case h::t => Some(Zipper(Nil, h, t)) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/main/scala/intro/CheatSheet.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /* 4 | This source file compiles with scalac: 5 | 6 | > scalac CheatSheet.scala 7 | 8 | The purpose of this document is to demonstrate various constructs in the Scala 9 | programming language. Commentary is associated with declarations throughout. 10 | 11 | The following is an object declaration. The term "object" is ambiguous in 12 | general discourse. The Scala keyword "object" is used to describe the 13 | declaration below. This document will call this an "object declaration", which 14 | is to be distinguished from any other usage of the term "object." 15 | 16 | Object declarations follow the object keyword with a name. That name is, by 17 | convention, made of alpha characters with the first in upper-case and the 18 | remainder in lower-case. 19 | 20 | Inside the open and close braces of an object declaration are a collection of 21 | values. This includes, var, val and def declarations. Less frequently, you will 22 | encounter type-aliases (with the type keyword) and declarations that may also 23 | appear at the top-level (trait, class, object). 24 | 25 | Object declarations in Scala are similar to static method declarations in 26 | languages such as Java or C#. They are not invoked on a specific instance (as in 27 | a class or trait declaration). Unlike Java or C#, these static functions are 28 | declared separately to a class or trait; that is, static functions belong in 29 | an object declaration, while instance values and methods appear in the class or 30 | trait. 31 | */ 32 | object CheatSheet { 33 | //// var keyword, variable 34 | /* 35 | The var keyword defines a variable that may be updated by other code that 36 | references it. The var keyword is followed by a colon (:), which is 37 | pronounced, "is of the type" and following the colon is the type of the var. 38 | So we might say, a "var age is of the type Int." Following the name and type 39 | declaration is a value using the equals sign (=) followed by the value. 40 | 41 | In functional programming, we generally avoid the use of the var keyword, 42 | however, some use-cases might demand it for performance reasons. Other value 43 | declarations such as val and def are generally preferred. 44 | */ 45 | var age: Int = 100 46 | 47 | //// def keyword, method 48 | /* 49 | The def keyword declares a method. It may (not necessarily) be followed by one 50 | or more argument declarations. In this case, one argument is defined, "newAge 51 | is of the type Int" and the parameter list is followed by a colon (:) and a 52 | return type declaration. 53 | 54 | Return type declarations are not always necessary in Scala because the 55 | language may infer the return type automatically. However, it is good practice 56 | to explicitly specify method return types. 57 | 58 | In this case, the return type is Unit. This is similar to void in languages 59 | like Java and C#. This is because we are performing a *side-effect* in the 60 | method. The side-effect is the updating of the variable x declared earlier. 61 | 62 | It must be emphasised that this type of programming — declaring and updating 63 | variables is generally avoided in functional programming. The variable 64 | declaration (age) and updating method (newAge) are included for completeness 65 | and to demonstrate the essence of what it means to NOT be functional 66 | programming. 67 | */ 68 | def updateAge(newAge: Int): Unit = 69 | age = newAge 70 | } 71 | 72 | //// top-level declaration 73 | /* 74 | Scala has a limited number of permitted top-level declarations. That is, 75 | declarations that appear on the outside and not within any other declaration. 76 | Top-level declarations may also appear within other declarations, but not all 77 | declarations may appear at top-level. 78 | */ 79 | 80 | //// object declaration 81 | /* 82 | Object declarations may appear at top-level. They use the object keyword, 83 | followed by its name, then enclosing braces. Within the braces are further 84 | declarations. Objects define 'static' declarations, which are not associated 85 | with an instance. They are invoked with the object name followed by the 86 | declaration name. 87 | */ 88 | object AnObject {} 89 | 90 | //// object declaration, main method 91 | /* 92 | To have a runnable Scala program, a method named 'main' must appear in a 93 | top-level object declaration and with a specific signature. 94 | */ 95 | object AnObjectWithMain { 96 | //// main method 97 | /* 98 | Note the absence of a = sign in the declaration. This means the function 99 | returns the type 'Unit'. We may choose to specify this explicitly, however, 100 | in the absence of a = sign, this is inferred. 101 | */ 102 | def main(args: Array[String]) { 103 | println("hi") 104 | } 105 | } 106 | 107 | /* 108 | Class declarations may appear at top-level. They use the class keyword, followed 109 | by its name, then optional constructor parameters, then enclosing braces. Within 110 | the braces are further declarations. Classes define instance declarations, which 111 | are associated with an instance of that class. They are invoked with the 112 | instance name followed by the declaration name. 113 | 114 | Declarations in a class must be concrete. That is, they must not be abstract. 115 | */ 116 | class AClass(params: Int) {} 117 | 118 | //// trait declaration 119 | /* 120 | Trait declarations may appear at top-level. They use the trait keyword, followed 121 | by its name, then enclosing braces. Within the braces are further declarations. 122 | Traits define instance declarations, which are associated with an instance of 123 | that trait. They are invoked with the instance name followed by the declaration 124 | name. 125 | 126 | Declarations in a trait may be concrete or abstract. 127 | */ 128 | trait ATrait {} 129 | 130 | //// case class declaration 131 | /* 132 | Case class declarations may appear at top-level. They use the case and class 133 | keywords, followed by its name, then optional constructor parameters, then 134 | enclosing braces. Within the braces are further declarations. Classes define 135 | instance declarations, which are associated with an instance of that class. They 136 | are invoked with the instance name followed by the declaration name. 137 | 138 | Case classes include a default 'equals', 'hashCode' and 'toString' method for 139 | instances. They may also be used in pattern-matching. 140 | 141 | Declarations in a class must be concrete. That is, they must not be abstract. 142 | */ 143 | case class ACaseClass(params: Int) {} 144 | 145 | //// extends keyword, with keyword 146 | /* 147 | Classes extend zero or one other class and zero or more traits. In this example, 148 | the 'Sub' class extends the 'Super1' class and mixes in the 'Super2' trait. 149 | */ 150 | class Super1 {} 151 | trait Super2 {} 152 | class Sub extends Super1 with Super2 {} 153 | 154 | 155 | //// sealed keyword, sealed trait declaration, algebraic data type 156 | /* 157 | Traits may be declared using the sealed keyword. This means that all subtypes of 158 | that trait appear in the same source file. Attempting to subtype a sealed trait 159 | outside of the source file results in a compile-time error. 160 | 161 | Sealed traits are used often to define Algebraic Data Types (ADTs). 162 | */ 163 | sealed trait SealedTrait {} 164 | 165 | //// type-parameter 166 | /* 167 | Scala declares type-parameters, similarly to Java. In Scala, they appear in 168 | [square brackets] where in Java, they appear in . Java uses the 169 | term "generics" to describe type-parameters. 170 | */ 171 | 172 | //// class type-parameter 173 | /* 174 | This class defines a single type-parameter which may be used inside the body of 175 | that class. We say 'the class is parameterised by A'. The position of the 176 | type-parameter is important to how it is used. That is, type-parameters 177 | appearing on a class have a scope over that class. Type-parameters defined 178 | elsewhere have different scopes. 179 | */ 180 | class AClassWithTypeParameter[A] {} 181 | 182 | //// method type-parameter 183 | /* 184 | This class defines a method that is parameterised with the name 'X'. This 185 | type-parameter has scope within the method body. 186 | */ 187 | class AClassWithATypeParameterAndParameterisedMethod[T] { 188 | def method[X] {} 189 | } 190 | 191 | //// higher-kinded type-parameter 192 | /* 193 | This class defines a type parameter that accepts another type-parameter. This 194 | means only certain values will work for that type-parameter. For example, 'Int' 195 | will not work because it does not accept one more type-parameter. Similarly, 196 | 'Map' will not work because it accepts two type-parameters. Values that accept 197 | one type-parameter are List, Set and Option. This is because they accept one 198 | type-parameter. 199 | 200 | The method in the class is parameterised on a type-parameter that accepts two 201 | type-parameters. This means values such as Map and Function1 will work, but not 202 | List, Option or Int. 203 | 204 | This notion of parameterising on values that accept type-parameters is called 205 | 'higher-kinded polymorphism.' We describe these values as 'type constructors.' 206 | So we might say that "F is a unary type constructor" because it is a 207 | parameterised value that accepts one more type-parameter. 208 | */ 209 | class AClassWithHigherKind[F[_]] { 210 | def method[G[_, _]] {} 211 | } 212 | 213 | //// method declaration 214 | /* 215 | Methods are defined in objects, traits, classes or even inside methods. They use 216 | the def keyword. 217 | */ 218 | class Methods { 219 | /* 220 | This method accepts one parameter of the type 'Int' and returns a value of the 221 | type 'String'. Concrete methods, specifed by the = sign, return a value inside 222 | the following method body. 223 | */ 224 | def method1(x: Int): String = { 225 | "abc" 226 | } 227 | 228 | //// implicit method argument 229 | /* 230 | Methods may accept implicit parameters. These are parameters that are 231 | optionally passed at the call site. The value of the parameter that is passed 232 | is resolved at compile-time. The compiler will inspect the program scope for a 233 | value of the correct type, in this case, List[Int] and use that value. If 234 | there is no value in scope, or there are two or more values in scope, the 235 | compiler will fail with a message. 236 | */ 237 | def method2(x: Int)(implicit q: List[Int]): Long = { 238 | 5L 239 | } 240 | 241 | //// by-name method arguments 242 | /* 243 | Methods may pass parameters 'by-name.' This means that the value is not 244 | evaluated at run-time until needed. This is sometimes loosely called 245 | 'lazy evaluation.' By-name evaluation often affects program termination 246 | results and program performance. 247 | */ 248 | def method3(x: => Int): String = { 249 | "def" 250 | } 251 | } 252 | 253 | //// syntax 254 | /* 255 | Scala provides a wide variety of syntax. Following are a few of those. 256 | */ 257 | object Syntax { 258 | //// if/else expression 259 | /* 260 | Producing a value, based on some Boolean value, is done using if/else syntax. 261 | These two keywords emulate the C/Java ternary operator. The type of the 262 | expression is the least common supertype of each of the branches. 263 | */ 264 | val x: Int = if(true) 7 else 8 265 | 266 | //// if expressions 267 | /* 268 | Producing an effect, based on some Boolean value, is done using if syntax. 269 | This keyword alone is anti-thetical to functional programming, since it 270 | deviates from producing a value; instead, it produces an effect. The type of 271 | the expression is 'Unit.' 272 | */ 273 | val y = if(true) println("true") 274 | 275 | //// for-comprehension 276 | /* 277 | For-comprehensions are done with the for/yield keywords and <- syntax. They 278 | expand to method calls on an instance. In this case, the for-comprehension 279 | expands to: 280 | 281 | Some(7).flatMap(a => 282 | Some(8).map(b => 283 | a + b)) 284 | 285 | For-comprehensions are also called monad-comprehensions. They are similar to 286 | LINQ comprehensions in C# or do-notation in Haskell. 287 | */ 288 | val f: Option[Int] = 289 | for { 290 | a <- Some(7) 291 | b <- Some(8) 292 | } yield a + b 293 | 294 | //// pattern-matching, algebraic data type 295 | /* 296 | Pattern-matching is done with the match/case keywords. Pattern-matching is 297 | performed on special types of declaration called an algebraic data type. 298 | Algebraic Data Types are typically defined by a sealed trait, then one or more 299 | case class subtypes of that trait. These subtypes are sometimes called 300 | 'data constructors' or just 'constructors' for that type. 301 | 302 | 303 | Option is an algebraic data type with two constructors called Some and None. 304 | Pattern-matching a value of the type Option is used to switch on which 305 | constructor was used to create that Option value. 306 | 307 | Algebraic data types are sometimes called 'sums' or 'sum types.' This is 308 | because they correspond to addition in algebra. 309 | */ 310 | def m(option: Option[Int]): Int = 311 | option match { 312 | case Some(n) => 7 313 | case None => 8 314 | } 315 | 316 | //// tuple 317 | /* 318 | Scala provides syntax for tuples (sometimes called products because they 319 | correspond to multiplication in algebra). A tuple is 0 or more values and they 320 | can be of various types. This case defines a tuple of 2 values of the types 321 | Int and String. 322 | */ 323 | val t: (Int, String) = (7, "abc") 324 | } 325 | 326 | object TypeAlias { 327 | //// type alias 328 | /* 329 | Scala defines type-aliases using the type keyword. In this case, X is aliased 330 | to List[Char]. This means that wherever the type 'X' appears, the compiler 331 | may treat it as a List[Char]. This is demonstrated in the method, where the 332 | value of the type X is pattern-matched (List has two data constructors) and a 333 | Char is returned. 334 | */ 335 | type X = List[Char] 336 | 337 | def method(x: X): Char = 338 | x match { 339 | case Nil => 'x' 340 | case h :: _ => h 341 | } 342 | } 343 | 344 | //// function literal 345 | object FunctionLiterals { 346 | //// function as method argument 347 | /* 348 | Scala treats functions as first-class. In this case, the method accepts a 349 | value that is a function. That function must accept an Int and return a 350 | String to pass the compiler. The method body then uses that function by 351 | applying it to a value. 352 | 353 | Function parameters and values use => syntax. 354 | */ 355 | def method(k: Int => String): String = 356 | k(9) 357 | 358 | //// function literal 359 | /* 360 | This method calls a method that accepts a function argument. The function 361 | argument is created by an expression that names the argument (n), followed by 362 | => syntax, then the value to return from that function; in this case, a 363 | String. 364 | */ 365 | def callmethod: String = 366 | method((n: Int) => (n + 100).toString.reverse) 367 | } 368 | -------------------------------------------------------------------------------- /src/main/scala/intro/Equal.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /** 4 | * Type safe equality. 5 | */ 6 | sealed trait Equal[A] { 7 | def equal(a1: A, a2: A): Boolean 8 | } 9 | 10 | object Equal { 11 | /** 12 | * Convenience for summoning an Equal instance. 13 | * 14 | * usage: Equal[Int].equal(1, 2) 15 | */ 16 | def apply[A: Equal]: Equal[A] = 17 | implicitly[Equal[A]] 18 | 19 | /** 20 | * Convenience for constructing an Equal instance from 21 | * a function. 22 | */ 23 | def from[A](f: (A, A) => Boolean): Equal[A] = 24 | new Equal[A] { def equal(a1: A, a2: A) = f(a1, a2) } 25 | 26 | /** 27 | * Convenience for constructing an Equal instance from 28 | * built in scala equals. 29 | */ 30 | def derived[A]: Equal[A] = 31 | from[A](_ == _) 32 | 33 | /* Equal Instances */ 34 | 35 | implicit def StringEqual = 36 | derived[String] 37 | 38 | implicit def IntEqual = 39 | derived[Int] 40 | 41 | implicit def OptionEqual[A: Equal]: Equal[Option[A]] = 42 | from[Option[A]]({ 43 | case (Some(a1), Some(a2)) => Equal[A].equal(a1, a2) 44 | case (None, None) => true 45 | case (None, Some(_)) => false 46 | case (Some(_), None) => false 47 | }) 48 | 49 | implicit def OptionalEqual[A: Equal]: Equal[Optional[A]] = 50 | from[Optional[A]]({ 51 | case (Full(a1), Full(a2)) => Equal[A].equal(a1, a2) 52 | case (Empty(), Empty()) => true 53 | case (Empty(), Full(_)) => false 54 | case (Full(_), Empty()) => false 55 | }) 56 | 57 | implicit def ListEqual[A: Equal] = 58 | from[List[A]](_.corresponds(_)(Equal[A].equal)) 59 | 60 | implicit def Tuple2Equal[A: Equal, B: Equal] = 61 | from[(A, B)]({ 62 | case ((a1, b1), (a2, b2)) => 63 | Equal[A].equal(a1, a2) && Equal[B].equal(b1, b2) 64 | }) 65 | 66 | implicit def Tuple3Equal[A: Equal, B: Equal, C: Equal] = 67 | from[(A, B, C)]({ 68 | case ((a1, b1, c1), (a2, b2, c2)) => 69 | Equal[A].equal(a1, a2) && Equal[B].equal(b1, b2) && Equal[C].equal(c1, c2) 70 | }) 71 | 72 | implicit def ThrowableEqual = 73 | derived[Throwable] 74 | } 75 | 76 | /** 77 | * Syntactic support for the Equal type class. 78 | * 79 | * Anywhere this is in scope the `===` operator 80 | * can be used for type safe equality. 81 | * 82 | * Usage: 83 | * import EqualSyntax._ 84 | * 85 | * 1 === 2 86 | * 87 | * 1 === "hello" // doesn't compile 88 | */ 89 | object EqualSyntax { 90 | implicit class AnyEqualSyntax[A: Equal](value: A) { 91 | def ===(other: A) = 92 | Equal[A].equal(value, other) 93 | } 94 | } 95 | 96 | /** 97 | * Type classes should have laws, these are the laws for the 98 | * Equal type class. 99 | */ 100 | object EqualLaws { 101 | def commutative[A: Equal](a1: A, a2: A): Boolean = 102 | Equal[A].equal(a1, a2) == Equal[A].equal(a2, a1) 103 | 104 | def reflexive[A: Equal](f: A): Boolean = 105 | Equal[A].equal(f, f) 106 | 107 | def transitive[A: Equal](a1: A, a2: A, a3: A): Boolean = 108 | !(Equal[A].equal(a1, a2) && Equal[A].equal(a2, a3)) || Equal[A].equal(a1, a3) 109 | } 110 | 111 | /** 112 | * Example usage. 113 | */ 114 | object EqualExample { 115 | import EqualSyntax._ 116 | 117 | 1 === 1 118 | 119 | // 1 === "hello" -- doesn't compile 120 | 121 | def filterLike[A: Equal](a: A, xs: List[A]) = 122 | xs.filter(x => x === a) 123 | 124 | filterLike(1, List(1, 2, 3)) // List(1) 125 | 126 | // filterLike(1.0d, List(1.0d, 2.0d)) -- doesn't compile because we didn't define an instance for double 127 | } 128 | -------------------------------------------------------------------------------- /src/main/scala/intro/Functor.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /** 4 | * Abstraction of types that can be mapped over. 5 | * 6 | * The following laws should hold: 7 | * 8 | * 1) map(r)(z => z) == r 9 | * 2) map(r)(z => f(g(z))) == map(map(r)(g))(f) 10 | */ 11 | trait Functor[F[_]] { 12 | def map[A, B](a: F[A])(f: A => B): F[B] 13 | } 14 | 15 | object Functor { 16 | /** 17 | * Convenience for summoning a Functor instance. 18 | * 19 | * usage: Functor[Int].map(1, 2) 20 | */ 21 | def apply[F[_]: Functor]: Functor[F] = 22 | implicitly[Functor[F]] 23 | 24 | /* Functor Instances (cheating is good) */ 25 | 26 | implicit def IdFunctor: Functor[Id] = new Functor[Id] { 27 | def map[A, B](a: Id[A])(f: A => B) = 28 | a.map(f) 29 | } 30 | 31 | implicit def OptionFunctor: Functor[Option] = new Functor[Option] { 32 | def map[A, B](a: Option[A])(f: A => B) = 33 | a.map(f) 34 | } 35 | 36 | implicit def OptionalFunctor: Functor[Optional] = new Functor[Optional] { 37 | def map[A, B](a: Optional[A])(f: A => B) = 38 | a.map(f) 39 | } 40 | 41 | implicit def ListFunctor: Functor[List] = new Functor[List] { 42 | def map[A, B](a: List[A])(f: A => B) = 43 | Lists.map(a)(f) 44 | } 45 | 46 | /* Functor Library */ 47 | 48 | /** Exercise: 1 - Twin all `A`s in `fa`. */ 49 | def fpair[F[_]: Functor, A](fa: F[A]): F[(A, A)] = 50 | ??? 51 | 52 | /** Exercise: 2 - Pair all `A`s in `fa` with the result of function application. */ 53 | def fproduct[F[_]: Functor, A, B](fa: F[A])(f: A => B): F[(A, B)] = 54 | ??? 55 | 56 | /** Exercise: 3 - Inject `a` to the left of `B`s in `f`. */ 57 | def strengthL[F[_]: Functor, A, B](a: A, f: F[B]): F[(A, B)] = 58 | ??? 59 | 60 | /** Exercise: 4 - Inject `b` to the right of `A`s in `f`. */ 61 | def strengthR[F[_]: Functor, A, B](f: F[A], b: B): F[(A, B)] = 62 | ??? 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/intro/Id.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /** 4 | * `Id` is one of the most basic parameterized types. It 5 | * is simply a container for some value `A`. 6 | */ 7 | case class Id[A](value: A) { 8 | 9 | /* 10 | * Implement map for Id[A]. 11 | * 12 | * The following laws must hold: 13 | * 1) r.map(z => z) == r 14 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 15 | * 16 | * scala> Id(1).map(x => x + 10) 17 | * = Id(11) 18 | */ 19 | def map[B](f: A => B): Id[B] = 20 | ??? 21 | 22 | /* 23 | * Implement flatMap. 24 | * 25 | * The following law must hold: 26 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 27 | * 28 | * scala> Id(1).flatMap(x => Id(x + 10)) 29 | * = Id(11) 30 | */ 31 | def flatMap[B](f: A => Id[B]): Id[B] = 32 | ??? 33 | } 34 | 35 | object Id { 36 | /* 37 | * Implement point. 38 | * 39 | * scala> point(1) 40 | * = Id(1) 41 | */ 42 | def point[A](v: A): Id[A] = 43 | ??? 44 | } 45 | -------------------------------------------------------------------------------- /src/main/scala/intro/Intro.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | object Intro { 4 | /* 5 | * There are two important questions: 6 | * - what is functional programming? (10 minutes) 7 | * - why do we care? (10 years) 8 | */ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | /* 41 | * functional programming is programming with functions.... 42 | */ 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | /* 75 | * What is a function? 76 | * 77 | * A function relates every set of arguments to a result and does 78 | * _nothing_ else. 79 | * 80 | * This means if you pass the _same_ arguments, you must get the 81 | * _same_ result. 82 | */ 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | /* 117 | * Integer addition 118 | */ 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | /* 153 | * String concatenation 154 | */ 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | /* 189 | * String builder append 190 | */ 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | /* 225 | * This gives us equational reasoning. This is what is 226 | * meant by can we "reason" about this code. It does not 227 | * imply that your code is good or bad, just that either 228 | * way we have a really effective tool to help us understand 229 | * and modify the code with out fear. 230 | */ 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | /* 265 | * This really is it. Everything else is sugar (or vinegar 266 | * depending on your perspective). At times things may seem 267 | * obtuse, but I ask you to suspend any disbelief, all we are 268 | * trying to do is approach programming with functions in a 269 | * pervasive fashion. 270 | * 271 | * The rest of the exercises will start to give you an idea of 272 | * what this is like, it will show how using types can assist, 273 | * and hopefully start to give you an idea of why functional 274 | * programming. 275 | */ 276 | } 277 | -------------------------------------------------------------------------------- /src/main/scala/intro/List.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | object Lists { 4 | 5 | /* 6 | * 7 | * The following examples are all based upon the 'List' 8 | * data structure. We will be using the List implementation 9 | * from the standard library. 10 | * 11 | * In scala this data structure looks like this: 12 | * 13 | * {{{ 14 | * sealed trait List[+A] 15 | * case object Nil extends List[Nothing] 16 | * case class ::[A](h: A, t: List[A]) extends List[A] 17 | * }}} 18 | * 19 | * We call this a "sum-type", where "List" is a type 20 | * constructor that has two data constructors, "Nil", 21 | * and "::" (pronounced ~cons~). We can declare values 22 | * of type List using either the data constructors or 23 | * via the convenience function `List`. 24 | * 25 | * {{{ 26 | * val xs = "goodbye" :: "cruel" :: "world" :: Nil 27 | * val ys = List("we", "have", "the", "technology") 28 | * }}} 29 | * 30 | * Lists can be worked with via pattern matching or via 31 | * the standard library methods foldRight & foldLeft. 32 | * These are defined as: 33 | * 34 | * {{{ 35 | * List[A]#foldRight[B](z: B)(f: (A, B) => B) 36 | * List[A]#foldLeft[B](z: B)(f: (B, A) => B) 37 | * }}} 38 | * 39 | */ 40 | 41 | 42 | /* 43 | * Exercise 1: 44 | * 45 | * Implement length using pattern matching. 46 | * 47 | * scala> Lists.length(List(1, 2, 3, 4)) 48 | * resX: Int = 4 49 | */ 50 | def length[A](xs: List[A]): Int = 51 | ??? 52 | 53 | /* 54 | * Exercise 2: 55 | * 56 | * Implement length using foldRight. 57 | * 58 | * scala> Lists.lengthX(List(1, 2, 3, 4)) 59 | * resX: Int = 4 60 | */ 61 | def lengthX[A](xs: List[A]): Int = 62 | ??? 63 | 64 | /* 65 | * Exercise 3: 66 | * 67 | * Append two lists to produce a new list. 68 | * 69 | * scala> Lists.append(List(1, 2, 3, 4), List(5, 6, 7, 8)) 70 | * resX: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8) 71 | */ 72 | def append[A](x: List[A], y: List[A]): List[A] = 73 | ??? 74 | 75 | /* 76 | * Exercise 4: 77 | * 78 | * Map the given function across each element of the list. 79 | * 80 | * scala> Lists.map(List(1, 2, 3, 4))(x => x + 1) 81 | * resX: List[Int] = List(2, 3, 4, 5) 82 | * 83 | * ~~~ Syntax hint: type annotations 84 | * 85 | * (Nil : List[A]) // Nil _is of type_ List[A] 86 | * 87 | * Type annotations are required when scala can 88 | * not infer what you mean. 89 | */ 90 | def map[A, B](xs: List[A])(f: A => B): List[B] = 91 | ??? 92 | 93 | /* 94 | * Exercise 5: 95 | * 96 | * Return elements satisfying the given predicate. 97 | * 98 | * scala> Lists.filter(List(1, 2, 3, 4))(i => i < 3) 99 | * resX: List[Int] = List(1, 2) 100 | */ 101 | def filter[A](xs: List[A])(p: A => Boolean): List[A] = 102 | ??? 103 | 104 | /* 105 | * Exercise 6: 106 | * 107 | * Reverse a list to produce a new list. 108 | * 109 | * scala> Lists.reverse(List( 1, 2, 3, 4)) 110 | * resX: List[Int] = List(4, 3, 2, 1) 111 | * 112 | * ~~~ Syntax hint: type annotations 113 | * 114 | * (Nil : List[A]) // Nil _is of type_ List[A] 115 | * 116 | * Type annotations are required when scala can 117 | * not infer what you mean. 118 | */ 119 | def reverse[A](xs: List[A]): List[A] = 120 | ??? 121 | 122 | 123 | /* 124 | * *Challenge* Exercise 7: 125 | * 126 | * Sequence a list of Option into an Option of Lists by producing 127 | * Some of a list of all the values or returning None on the first 128 | * None case. 129 | * 130 | * scala> Lists.sequence(List[Option[Int]](Some(1), Some(2), Some(3))) 131 | * resX: Option[List[Int]] = Some(List(1, 2, 3)) 132 | * 133 | * scala> Lists.sequence(List[Option[Int]](Some(1), None, Some(3))) 134 | * resX: Option[List[Int]] = None 135 | */ 136 | def sequence[A](xs: List[Option[A]]): Option[List[A]] = 137 | ??? 138 | 139 | /* 140 | * *Challenge* Exercise 8: 141 | * 142 | * Return a list of ranges. A range is a pair of values for which each 143 | * intermediate value exists in the list. 144 | * 145 | * 146 | * scala> Lists.ranges(List(1, 2, 3, 4, 7, 8, 9, 10, 30, 40, 41)) 147 | * resX: List[(Int, Int)] = List((1, 4), (7, 10), (30, 30), (40, 41)) 148 | * 149 | * scala> Lists.ranges(List(1, 2, 3, 4)) 150 | * resX: List[(Int, Int)] = List((1, 4)) 151 | * 152 | * scala> Lists.ranges(List(1, 2, 4)) 153 | * resX: List[(Int, Int)] = List((1, 2), List(4, 4)) 154 | * 155 | * scala> Lists.ranges(List(2, 1, 3, 4, 9, 7, 8, 10, 30, 30, 4, 41)) 156 | * resX: List[(Int, Int)] = List((1, 4), (7, 10), (30, 30), (40, 41)) 157 | * 158 | * ~~~ library hint: use can just use List[A]#sorted and/or List[A]#reverse to 159 | * get the list in the correct order. * 160 | */ 161 | def ranges(xs: List[Int]): List[(Int, Int)] = 162 | ??? 163 | } 164 | -------------------------------------------------------------------------------- /src/main/scala/intro/Monoid.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /** 4 | * A monoid is an identity element paired with an associative 5 | * binary operation such that the following laws hold: 6 | * 7 | * associative: 8 | * op(a, op(b, c)) == op(op(a, b), c) 9 | * 10 | * right identity: 11 | * op(a, identity) == a 12 | * 13 | * left identity: 14 | * op(identity, a) == a 15 | */ 16 | trait Monoid[A] { 17 | def identity: A 18 | def op(x: A, y: A): A 19 | } 20 | 21 | /* Useful datatypes that have a Monoid */ 22 | case class Sum(n: Int) 23 | case class Product(n: Int) 24 | case class Min(n: Int) 25 | case class Max(n: Int) 26 | case class Size(n: Int) 27 | case class Endo[A](f: A => A) 28 | case class First[A](first: Option[A]) 29 | case class Last[A](last: Option[A]) 30 | 31 | object Monoid { 32 | /** 33 | * Convenience for summoning a Monoid instance. 34 | * 35 | * usage: Monoid[String].op("yo", "lo") 36 | */ 37 | def apply[A: Monoid]: Monoid[A] = 38 | implicitly[Monoid[A]] 39 | 40 | /* Monoid instances */ 41 | 42 | /** Exercise 1: A monoid which takes the sum of the underlying integer values */ 43 | implicit def SumMonoid: Monoid[Sum] = 44 | ??? 45 | 46 | /** Exercise 2: A monoid which takes the multiplication of the underlying integer values */ 47 | implicit def ProductMonoid: Monoid[Product] = 48 | ??? 49 | 50 | /** Exercise 3: A monoid which takes the minimum of the underlying integer values */ 51 | implicit def MinMonoid: Monoid[Min] = 52 | ??? 53 | 54 | /** Exercise 4: A monoid which takes the maximum of the underlying integer values */ 55 | implicit def MaxMonoid: Monoid[Max] = 56 | ??? 57 | 58 | /** Exercise 5: A monoid which counts the number of underlying values */ 59 | implicit def SizeMonoid: Monoid[Size] = 60 | ??? 61 | 62 | /** Exercise 6: A monoid which composes the underlying functions */ 63 | implicit def EndoMonoid[A]: Monoid[Endo[A]] = 64 | ??? 65 | 66 | /** Exercise 7: A monoid which always takes the first value */ 67 | implicit def FirstMonoid[A]: Monoid[First[A]] = 68 | ??? 69 | 70 | /** Exercise 8: A monoid which always takes the last value */ 71 | implicit def LastMonoid[A]: Monoid[Last[A]] = 72 | ??? 73 | 74 | /** Exercise 9: A monoid which concatenates lists */ 75 | implicit def ListMonoid[A]: Monoid[List[A]] = 76 | ??? 77 | 78 | /** Exercise 10: A monoid which unions the map, applying op to merge values */ 79 | implicit def MapMonoid[A, B: Monoid]: Monoid[Map[A, B]] = 80 | ??? 81 | 82 | /* Monoid library */ 83 | 84 | import MonoidSyntax._ 85 | 86 | /** 87 | * Exercise 11 88 | * 89 | * Accumulate a value by summing the result of the map function `f` 90 | * applied to each element. 91 | * 92 | * scala> foldMap(List(1, 2, 3, 4, 5))(x => Sum(x)) 93 | * = Sum(15) 94 | */ 95 | def foldMap[A, B: Monoid](xs: List[A])(f: A => B): B = 96 | ??? 97 | 98 | /** 99 | * Exercise 12 100 | * 101 | * Sum all the elements in the list using the Monoid for A. 102 | * 103 | * scala> sum(List("hello", " ", "world")) 104 | * = "hello world" 105 | */ 106 | def sum[A: Monoid](xs: List[A]): A = 107 | ??? 108 | } 109 | 110 | object MonoidSyntax { 111 | implicit class AnyMonoidSyntax[A: Monoid](value: A) { 112 | def |+|(other: A) = 113 | Monoid[A].op(value, other) 114 | } 115 | } 116 | 117 | /** 118 | * *Challenge* Exercise 13 119 | * 120 | * We have a data set of ticker prices. Produces a set of statistics for each 121 | * unique ticker name. 122 | */ 123 | object MonoidChallenge { 124 | import MonoidSyntax._ 125 | import MonoidTupleInstances._ 126 | 127 | case class Stats(min: Int, max: Int, total: Int, count: Int, average: Int) 128 | case class Stock(ticker: String, date: String, cents: Int) 129 | 130 | /** 131 | * Compute a map of ticker -> stats from the given data, ignoring any data 132 | * points that do _not_ match predicate. 133 | * 134 | * We want to do this with a single pass over the input data (doing a secondary 135 | * pass across the output is ok), so do not use list.filter(..). 136 | * 137 | * Example, filter out 0 values as they are bad data: 138 | * MonoidChallenge.compute(MonoidChallenge.Data, stock => stock.cents != 0) 139 | * 140 | * Example, only include particular days of data: 141 | * MonoidChallenge.compute(MonoidChallenge.Data, stock => stock.date == "2012-01-01" || stock.date == "2012-01-02") 142 | * 143 | * Note there are monoid instances for tuples whos components are all monoids 144 | * (this may be useful, but is not the only way to solve this problem). 145 | */ 146 | def compute(data: List[Stock], predicate: Stock => Boolean): Map[String, Stats] = 147 | ??? 148 | 149 | def Data = List( 150 | Stock("FAKE", "2012-01-01", 10000) 151 | , Stock("FAKE", "2012-01-02", 10020) 152 | , Stock("FAKE", "2012-01-03", 10022) 153 | , Stock("FAKE", "2012-01-04", 10005) 154 | , Stock("FAKE", "2012-01-05", 9911) 155 | , Stock("FAKE", "2012-01-06", 6023) 156 | , Stock("FAKE", "2012-01-07", 7019) 157 | , Stock("FAKE", "2012-01-08", 0) 158 | , Stock("FAKE", "2012-01-09", 7020) 159 | , Stock("FAKE", "2012-01-10", 7020) 160 | , Stock("CAKE", "2012-01-01", 1) 161 | , Stock("CAKE", "2012-01-02", 2) 162 | , Stock("CAKE", "2012-01-03", 3) 163 | , Stock("CAKE", "2012-01-04", 4) 164 | , Stock("CAKE", "2012-01-05", 5) 165 | , Stock("CAKE", "2012-01-06", 6) 166 | , Stock("CAKE", "2012-01-07", 7) 167 | , Stock("BAKE", "2012-01-01", 99999) 168 | , Stock("BAKE", "2012-01-02", 99999) 169 | , Stock("BAKE", "2012-01-03", 99999) 170 | , Stock("BAKE", "2012-01-04", 99999) 171 | , Stock("BAKE", "2012-01-05", 99999) 172 | , Stock("BAKE", "2012-01-06", 0) 173 | , Stock("BAKE", "2012-01-07", 99999) 174 | , Stock("LAKE", "2012-01-01", 10012) 175 | , Stock("LAKE", "2012-01-02", 7000) 176 | , Stock("LAKE", "2012-01-03", 1234) 177 | , Stock("LAKE", "2012-01-04", 10) 178 | , Stock("LAKE", "2012-01-05", 6000) 179 | , Stock("LAKE", "2012-01-06", 6099) 180 | , Stock("LAKE", "2012-01-07", 5999) 181 | ) 182 | } 183 | 184 | 185 | object MonoidTupleInstances { 186 | 187 | /** A monoid for Tuple2 that merges each element with op */ 188 | implicit def Tuple2Monoid[A: Monoid, B: Monoid]: Monoid[(A, B)] = 189 | new Monoid[(A, B)] { 190 | def identity = 191 | (Monoid[A].identity, Monoid[B].identity) 192 | 193 | def op(x: (A, B), y: (A, B)) = 194 | (x, y) match { 195 | case ((a1, b1), (a2, b2)) => 196 | (Monoid[A].op(a1, a2), Monoid[B].op(b1, b2)) 197 | } 198 | } 199 | 200 | /** A monoid for Tuple3 that merges each element with op */ 201 | implicit def Tuple3Monoid[A: Monoid, B: Monoid, C: Monoid]: Monoid[(A, B, C)] = 202 | new Monoid[(A, B, C)] { 203 | def identity = 204 | (Monoid[A].identity, Monoid[B].identity, Monoid[C].identity) 205 | 206 | def op(x: (A, B, C), y: (A, B, C)) = 207 | (x, y) match { 208 | case ((a1, b1, c1), (a2, b2, c2)) => 209 | (Monoid[A].op(a1, a2), Monoid[B].op(b1, b2), Monoid[C].op(c1, c2)) 210 | } 211 | } 212 | 213 | /** A monoid for Tuple4 that merges each element with op */ 214 | implicit def Tuple4Monoid[A: Monoid, B: Monoid, C: Monoid, D: Monoid]: Monoid[(A, B, C, D)] = 215 | new Monoid[(A, B, C, D)] { 216 | def identity = 217 | (Monoid[A].identity, Monoid[B].identity, Monoid[C].identity, Monoid[D].identity) 218 | 219 | def op(x: (A, B, C, D), y: (A, B, C, D)) = 220 | (x, y) match { 221 | case ((a1, b1, c1, d1), (a2, b2, c2, d2)) => 222 | (Monoid[A].op(a1, a2), Monoid[B].op(b1, b2), Monoid[C].op(c1, c2), Monoid[D].op(d1, d2)) 223 | } 224 | } 225 | 226 | /** A monoid for Tuple5 that merges each element with op */ 227 | implicit def Tuple5Monoid[A: Monoid, B: Monoid, C: Monoid, D: Monoid, E: Monoid]: Monoid[(A, B, C, D, E)] = 228 | new Monoid[(A, B, C, D, E)] { 229 | def identity = 230 | (Monoid[A].identity, Monoid[B].identity, Monoid[C].identity, Monoid[D].identity, Monoid[E].identity) 231 | 232 | def op(x: (A, B, C, D, E), y: (A, B, C, D, E)) = 233 | (x, y) match { 234 | case ((a1, b1, c1, d1, e1), (a2, b2, c2, d2, e2)) => 235 | (Monoid[A].op(a1, a2), Monoid[B].op(b1, b2), Monoid[C].op(c1, c2), Monoid[D].op(d1, d2), Monoid[E].op(e1, e2)) 236 | } 237 | } 238 | 239 | /** A monoid for Tuple6 that merges each element with op */ 240 | implicit def Tuple6Monoid[A: Monoid, B: Monoid, C: Monoid, D: Monoid, E: Monoid, F: Monoid]: Monoid[(A, B, C, D, E, F)] = 241 | new Monoid[(A, B, C, D, E, F)] { 242 | def identity = 243 | (Monoid[A].identity, Monoid[B].identity, Monoid[C].identity, Monoid[D].identity, Monoid[E].identity, Monoid[F].identity) 244 | 245 | def op(x: (A, B, C, D, E, F), y: (A, B, C, D, E, F)) = 246 | (x, y) match { 247 | case ((a1, b1, c1, d1, e1, f1), (a2, b2, c2, d2, e2, f2)) => 248 | (Monoid[A].op(a1, a2), Monoid[B].op(b1, b2), Monoid[C].op(c1, c2), Monoid[D].op(d1, d2), Monoid[E].op(e1, e2), Monoid[F].op(f1, f2)) 249 | } 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/main/scala/intro/Optional.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | 4 | /* 5 | * Optional is a data type used to represent a value which may 6 | * or may not be set. An optional may either be _Full_ and contain 7 | * a value _or_ be _Empty_ and not contain a value. 8 | * 9 | * This is directly equivalent to the Option type in the scala 10 | * standard library where Some -> Full, and None -> Empty. 11 | */ 12 | sealed trait Optional[A] { 13 | /* 14 | * Implement fold for Optional. 15 | * 16 | * We often want to work with data structures be breaking them 17 | * down by cases. All algebraic data structures can be broken 18 | * down in the same way, a function is provided to handle each 19 | * case, with the arguments to the data constructors matching the 20 | * arguments to the fold functions. 21 | * 22 | * scala> Full(1).fold(x => x, 0) 23 | * = 1 24 | * 25 | * scala> Empty().fold(x => x, 0) 26 | * = 0 27 | */ 28 | def fold[X]( 29 | full: A => X, 30 | empty: => X 31 | ): X = 32 | ??? 33 | 34 | /* 35 | * Implement map for Optional[A]. 36 | * 37 | * The following laws must hold: 38 | * 1) r.map(z => z) == r 39 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 40 | * 41 | * scala> Full(1).map(x => x + 10) 42 | * = Full(11) 43 | * 44 | * scala> Empty[Int]().map(x => x + 10) 45 | * = Emptyy() 46 | */ 47 | def map[B](f: A => B): Optional[B] = 48 | ??? 49 | 50 | /* 51 | * Implement flatMap. 52 | * 53 | * The following law must hold: 54 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 55 | * 56 | * scala> Full(1).flatMap(x => Full(x + 10)) 57 | * = Full(11) 58 | * 59 | * scala> Full(1).flatMap(x => Empty[Int]()) 60 | * = Empty(Unauthorized) 61 | * 62 | * scala> Empty[Int]().map(x => Full(x + 10)) 63 | * = Empty() 64 | * 65 | * Advanced: Try using fold. 66 | */ 67 | def flatMap[B](f: A => Optional[B]): Optional[B] = 68 | ??? 69 | 70 | /* 71 | * Extract the value if it is success case otherwise use default value. 72 | * 73 | * 74 | * scala> Full(1).getOrElse(10) 75 | * = 1 76 | * 77 | * scala> Empty[Int]().getOrElse(10) 78 | * = 10 79 | */ 80 | def getOrElse(otherwise: => A): A = 81 | ??? 82 | 83 | /* 84 | * Implement choice, take this result if successful otherwise take 85 | * the alternative. 86 | * 87 | * scala> Full(1) ||| Full(10) 88 | * = Full(1) 89 | * 90 | * scala> Full(1) ||| Empty[Int]() 91 | * = Full(1) 92 | * 93 | * scala> Empty[Int]() ||| Full(10) 94 | * = Full(10) 95 | * 96 | * scala> Empty[Int]() ||| Empty[Int]() 97 | * = Empty() 98 | */ 99 | def |||(alternative: => Optional[A]): Optional[A] = 100 | ??? 101 | } 102 | 103 | case class Full[A](a: A) extends Optional[A] 104 | case class Empty[A]() extends Optional[A] 105 | 106 | object Optional { 107 | def empty[A]: Optional[A] = 108 | Empty() 109 | 110 | def ok[A](value: A): Optional[A] = 111 | Full(value) 112 | } 113 | -------------------------------------------------------------------------------- /src/main/scala/intro/Result.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | /* 4 | * Handling errors without exceptions.... 5 | * ====================================== 6 | */ 7 | 8 | /* 9 | * A well-typed set of errors that can occur. 10 | */ 11 | sealed trait Error 12 | case class NotANumber(s: String) extends Error 13 | case class InvalidOperation(s: String) extends Error 14 | case class UnexpectedInput(s: String) extends Error 15 | case object NotEnoughInput extends Error 16 | 17 | /* 18 | * A result type that represents one of our errors or a success. 19 | */ 20 | case class Fail[A](error: Error) extends Result[A] 21 | case class Ok[A](value: A) extends Result[A] 22 | 23 | sealed trait Result[A] { 24 | /* 25 | * Exercise 1: 26 | * 27 | * We often want to work with data structures by breaking them 28 | * down by cases. With lists, this operation is foldRight. For 29 | * our result type this is just called fold. More formally we 30 | * refer to this as a catamorphism. Implement fold for Result. 31 | * 32 | * Hint: Try using pattern matching. 33 | * 34 | * scala> Ok(1).fold(_ => 0, x => x) 35 | * = 1 36 | * 37 | * scala> Fail[Int](NotEnoughInput).fold(_ => 0, x => x) 38 | * = 0 39 | */ 40 | def fold[X]( 41 | fail: Error => X, 42 | ok: A => X 43 | ): X = 44 | ??? 45 | 46 | /* 47 | * Exercise 2: 48 | * 49 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 50 | * 51 | * The following law must hold: 52 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 53 | * 54 | * scala> Ok(1).flatMap(x => Ok(x + 10)) 55 | * = Ok(11) 56 | * 57 | * scala> Ok(1).flatMap(x => Fail[Int](NotEnoughInput)) 58 | * = Fail(NotEnoughInput) 59 | * 60 | * scala> Fail[Int](NotEnoughInput).flatMap(x => Ok(x + 10)) 61 | * = Fail(NotEnoughInput) 62 | * 63 | * scala> Fail[Int](NotEnoughInput).flatMap(x => Fail[Int](UnexpectedInput("?"))) 64 | * = Fail(NotEnoughInput) 65 | * 66 | * Advanced: Try using fold. 67 | */ 68 | def flatMap[B](f: A => Result[B]): Result[B] = 69 | ??? 70 | 71 | /* 72 | * Exercise 3: 73 | * 74 | * Implement map for Result[A]. 75 | * 76 | * The following laws must hold: 77 | * 1) r.map(z => z) == r 78 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 79 | * 80 | * scala> Ok(1).map(x => x + 10) 81 | * = Ok(11) 82 | * 83 | * scala> Fail[Int](NotEnoughInput).map(x => x + 10) 84 | * = Fail(NotEnoughInput) 85 | * 86 | * Advanced: Try using flatMap. 87 | */ 88 | def map[B](f: A => B): Result[B] = 89 | ??? 90 | 91 | /* 92 | * Exercise 4: 93 | * 94 | * Extract the value if it is success case otherwise use default value. 95 | * 96 | * scala> Ok(1).getOrElse(10) 97 | * = 1 98 | * 99 | * scala> Fail(NotEnoughInput).getOrElse(10) 100 | * = 10 101 | */ 102 | def getOrElse(otherwise: => A): A = 103 | ??? 104 | 105 | /* 106 | * Exercise 5: 107 | * 108 | * Implement choice, take this result if successful otherwise take 109 | * the alternative. 110 | * 111 | * scala> Ok(1) ||| Ok(10) 112 | * = Ok(1) 113 | * 114 | * scala> Ok(1) ||| Fail[Int](NotEnoughInput) 115 | * = Ok(1) 116 | * 117 | * scala> Fail[Int](NotEnoughInput) ||| Ok(10) 118 | * = Ok(10) 119 | * 120 | * scala> Fail[Int](NotEnoughInput) ||| Fail[Int](UnexpectedInput("?")) 121 | * = Fail[Int](UnexpectedInput("?")) 122 | */ 123 | def |||(alternative: => Result[A]): Result[A] = 124 | ??? 125 | } 126 | 127 | object Result { 128 | def notANumber[A](s: String): Result[A] = 129 | fail(NotANumber(s)) 130 | 131 | def unexpectedInput[A](s: String): Result[A] = 132 | fail(UnexpectedInput(s)) 133 | 134 | def notEnoughInput[A]: Result[A] = 135 | fail(NotEnoughInput) 136 | 137 | def ok[A](value: A): Result[A] = 138 | Ok(value) 139 | 140 | def fail[A](error: Error): Result[A] = 141 | Fail(error) 142 | 143 | /* 144 | * *Challenge* Exercise 6: 145 | * 146 | * Sequence a list of Result into an Result of Lists by producing 147 | * Ok of a list of all the values or returning Fail on the first 148 | * Fail case. 149 | * 150 | * scala> Lists.sequence(List[Result[Int]](Ok(1), Ok(2), Ok(3))) 151 | * resX: Result[List[Int]] = Ok(List(1, 2, 3)) 152 | * 153 | * scala> Lists.sequence(List[Result[Int]](Ok(1), Fail(NotEnoughInput), Ok(3))) 154 | * resX: Result[List[Int]] = Fail(NotEnoughInput) 155 | */ 156 | def sequence[A](xs: List[Result[A]]): Result[List[A]] = 157 | ??? 158 | } 159 | 160 | 161 | /* 162 | * *Challenge* Exercise 7: The worlds most trivial calculator. 163 | * 164 | * We are implementing a way to compute a number on the command line. 165 | * - The first argument is the operation, that is one of +, - or * 166 | * - The second argument is an integer, n 167 | * - The third argument is an integer, m 168 | * 169 | * Complete the implementation, some of the methods are provided 170 | * with type signatures to get started. 171 | */ 172 | object ResultExample { 173 | 174 | /** Simplified calculation data type. */ 175 | sealed trait Operation 176 | case object Plus extends Operation 177 | case object Minus extends Operation 178 | case object Multiply extends Operation 179 | 180 | /* 181 | * Parse an int if it is valid, otherwise fail with NotAnInt. 182 | * 183 | * Hint: Scala defines String#toInt, but warning it throws exceptions 184 | * if it is not a valid Int :| i.e. use try catch. 185 | */ 186 | def int(body: String): Result[Int] = 187 | ??? 188 | 189 | /* 190 | * Parse the operation if it is valid, otherwise fail with InvalidOperation. 191 | */ 192 | def operation(op: String): Result[Operation] = 193 | ??? 194 | 195 | /* 196 | * Compute an `answer`, by running operation for n and m. 197 | */ 198 | def calculate(op: Operation, n: Int, m: Int): Int = 199 | ??? 200 | 201 | /* 202 | * Attempt to compute an `answer`, by: 203 | * - parsing operation 204 | * - parsing n 205 | * - parsing m 206 | * - running calculation 207 | * 208 | * hint: use flatMap / map 209 | */ 210 | def attempt(op: String, n: String, m: String): Result[Int] = 211 | ??? 212 | 213 | /* 214 | * Run a calculation by pattern matching three elements off the input arguments, 215 | * parsing the operation, a value for n and a value for m. 216 | */ 217 | def run(args: List[String]): Result[Int] = 218 | ??? 219 | 220 | def main(args: Array[String]) = 221 | println(run(args.toList) match { 222 | case Ok(result) => s"result: ${result}" 223 | case Fail(error) => s"failed: ${error}" 224 | }) 225 | } 226 | -------------------------------------------------------------------------------- /src/main/scala/intro/Scala.scala: -------------------------------------------------------------------------------- 1 | package course 2 | 3 | 4 | /* 5 | * The following is a high level guide of what scala features we 6 | * need to understand to get started. 7 | */ 8 | object Scala { 9 | 10 | /* sbt & repl */ 11 | 12 | 13 | /* clases & objects */ 14 | 15 | 16 | /* methods, values */ 17 | 18 | 19 | /* procedures (don't) */ 20 | 21 | 22 | /* parameter lists curried / tupled */ 23 | 24 | 25 | /* functions */ 26 | 27 | 28 | /* parametricity */ 29 | 30 | 31 | /* case classes - product types */ 32 | 33 | 34 | /* case classes - sum types */ 35 | 36 | 37 | /* pattern matching */ 38 | 39 | 40 | /* there is lots more, for comprehensions, implicits, laziness / call-by-name, 41 | ... but this is enough to get started */ 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/patterns/Applicative.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | trait Applicative[F[_]] extends Functor[F] { 6 | def pure[A](a: => A): F[A] 7 | def ap[A, B](a: F[A])(f: F[A => B]): F[B] 8 | } 9 | 10 | object Applicative { 11 | def apply[F[_]: Applicative]: Applicative[F] = 12 | implicitly[Applicative[F]] 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/patterns/Http.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ // type classes 4 | 5 | /* 6 | * A http datatype that represents a number of read/write/state/result 7 | * like structure as a function. 8 | */ 9 | case class Http[A](run: (HttpRead, HttpState) => (HttpWrite, HttpState, HttpValue[A])) { 10 | 11 | /* 12 | * Exercise 8a.1: 13 | * 14 | * Implement map for Http[A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | */ 20 | def map[B](f: A => B): Http[B] = 21 | ??? 22 | 23 | /* 24 | * Exercise 8a.2: 25 | * 26 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 27 | * 28 | * The following law must hold: 29 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 30 | * 31 | */ 32 | def flatMap[B](f: A => Http[B]): Http[B] = 33 | ??? 34 | } 35 | 36 | object Http { 37 | /* 38 | * Exercise 8a.3: 39 | * 40 | * Implement value (a.k.a. return, point, pure). 41 | * 42 | * Hint: Try using Http constructor. 43 | */ 44 | def value[A](a: => A): Http[A] = 45 | ??? 46 | 47 | /* 48 | * Exercise 8a.4: 49 | * 50 | * Implement ask for Http, similar behaviour to Reader.ask. 51 | * 52 | * Hint: Try using Http constructor. 53 | */ 54 | def httpAsk: Http[HttpRead] = 55 | ??? 56 | 57 | /* 58 | * Exercise 8a.5: 59 | * 60 | * Implement get for Http, similar behaviour to State.get. 61 | * 62 | * Hint: Try using Http constructor. 63 | */ 64 | def httpGet: Http[HttpState] = 65 | ??? 66 | 67 | /* 68 | * Exercise 8a.6: 69 | * 70 | * Implement modify for Http, similar behaviour to State.modify. 71 | * 72 | * Hint: Try using Http constructor. 73 | */ 74 | def httpModify(f: HttpState => HttpState): Http[Unit] = 75 | ??? 76 | 77 | /* 78 | * Exercise 8a.7: 79 | * 80 | * Implement getBody. 81 | * 82 | * Hint: You may want to define some other combinators to help 83 | * that have not been specified yet, remember exercise 2 ask? 84 | */ 85 | def getBody: Http[String] = 86 | ??? 87 | 88 | /* 89 | * Exercise 8a.8: 90 | * 91 | * Implement addHeader. 92 | * 93 | * Hint: You may want to define some other combinators to help 94 | * that have not been specified yet, remember exercise 4 update? 95 | */ 96 | def addHeader(name: String, value: String): Http[Unit] = 97 | ??? 98 | 99 | /* 100 | * Exercise 8a.9: 101 | * 102 | * Implement log. 103 | * 104 | * Hint: Try using Http constructor. 105 | */ 106 | def log(message: String): Http[Unit] = 107 | ??? 108 | 109 | implicit def HttpMonad: Monad[Http] = 110 | new Monad[Http] { 111 | def point[A](a: => A) = Http.value(a) 112 | def bind[A, B](a: Http[A])(f: A => Http[B]) = a flatMap f 113 | } 114 | } 115 | 116 | object HttpExample { 117 | import Http._ 118 | 119 | /* 120 | * Exercise 8a.10: 121 | * 122 | * Implement echo http service. 123 | * 124 | * Echo service should: 125 | * return body as string, 126 | * add "content-type" header of "text/plain" 127 | * log a message with the length of the body in characters. 128 | * 129 | * Hint: Try using flatMap or for comprehensions. 130 | */ 131 | def echo: Http[String] = 132 | ??? 133 | } 134 | 135 | 136 | /** Data type wrapping up all http state data */ 137 | case class HttpState(resheaders: Headers) 138 | 139 | /** Data type wrapping up all http environment data */ 140 | case class HttpRead(method: Method, body: String, reqheaders: Headers) 141 | 142 | /** Data type wrapping up http log data */ 143 | case class HttpWrite(log: Vector[String]) { 144 | def ++(w: HttpWrite) = 145 | HttpWrite(log ++ w.log) 146 | } 147 | 148 | /** Monoid for HttpWrite (for completeness and convenience). */ 149 | object HttpWrite { 150 | implicit def HttpWriteMonoid: Monoid[HttpWrite] = new Monoid[HttpWrite] { 151 | def identity = HttpWrite(Vector()) 152 | def op(a: HttpWrite, b: HttpWrite) = a ++ b 153 | } 154 | } 155 | 156 | /** Headers data type. */ 157 | case class Headers(headers: Vector[(String, String)] = Vector()) { 158 | def :+(p: (String, String)) = 159 | Headers(headers :+ p) 160 | } 161 | 162 | /** Method data type. */ 163 | sealed trait Method 164 | case object Options extends Method 165 | case object Get extends Method 166 | case object Head extends Method 167 | case object Post extends Method 168 | case object Put extends Method 169 | case object Delete extends Method 170 | case object Trace extends Method 171 | case object Connect extends Method 172 | -------------------------------------------------------------------------------- /src/main/scala/patterns/HttpT.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | import MonadTrans._ 5 | 6 | object HttpTransformerStack { 7 | type V[A] = HttpValue[A] 8 | type SV[A] = StateT[V, HttpState, A] 9 | type WSV[A] = WriterT[SV, HttpWrite, A] 10 | type RWSV[A] = ReaderT[WSV, HttpRead, A] 11 | 12 | 13 | /** Type aliases to use with liftM */ 14 | type S_[F[_], A] = StateT[F, HttpState, A] 15 | type W_[F[_], A] = WriterT[F, HttpWrite, A] 16 | type R_[F[_], A] = ReaderT[F, HttpRead, A] 17 | 18 | } 19 | 20 | import HttpTransformerStack._ 21 | 22 | case class HttpT[A](run: RWSV[A]) { 23 | /* 24 | * Exercise 8b.1: 25 | * 26 | * Implement map for HttpT[A]. 27 | * 28 | * The following laws must hold: 29 | * 1) r.map(z => z) == r 30 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 31 | */ 32 | def map[B](f: A => B): HttpT[B] = 33 | ??? 34 | 35 | /* 36 | * Exercise 8b.2: 37 | * 38 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 39 | * 40 | * The following law must hold: 41 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 42 | * 43 | */ 44 | def flatMap[B](f: A => HttpT[B]): HttpT[B] = 45 | ??? 46 | } 47 | 48 | object HttpT { 49 | import ReaderT.{ask, local} 50 | import StateT.{modify, get} 51 | import WriterT.tell 52 | import MonadTrans.liftM 53 | 54 | /* 55 | * Exercise 8b.3: 56 | * 57 | * Implement value (a.k.a. return, point, pure). 58 | * 59 | * Hint: Try using Http constructor. 60 | */ 61 | def value[A](a: => A): HttpT[A] = 62 | ??? 63 | 64 | /* 65 | * Exercise 8b.4: 66 | * 67 | * Implement ask for a Http. 68 | * 69 | * Hint: Try using Http constructor and ReaderT ask. 70 | */ 71 | def httpAsk: HttpT[HttpRead] = 72 | ??? 73 | 74 | /* 75 | * Exercise 8b.5: 76 | * 77 | * Implement get for a Http. 78 | * 79 | * Hint: Try using Http constructor, StateT get MonadTrans.liftM (twice). 80 | * 81 | * Hint Hint: liftM type signature is: 82 | * 83 | * liftM[F[_[_], _], G[_], A](g: G[A]) 84 | * 85 | * Hint Hint Hint: You will have to explicitly specify types for liftM and 86 | * there are convenience type aliases above R_, and W_ above that will help. 87 | * 88 | * liftM[R_, WSV, HttpState](???): RWSV[HttpState] 89 | * liftM[W_, SV, HttpState](???): WSV[HttpState] 90 | */ 91 | def httpGet: HttpT[HttpState] = 92 | /** FREE ANSWER, so you don't get too hung up on syntax, next one is for you */ 93 | HttpT( 94 | liftM[R_, WSV, HttpState]( 95 | liftM[W_, SV, HttpState]( 96 | get[V, HttpState]))) 97 | 98 | /* 99 | * Exercise 8b.6: 100 | * 101 | * Implement modify for a Http. 102 | * 103 | * Hint: Try using Http constructor, StateT modify MonadTrans.liftM (twice). 104 | * 105 | * Hint Hint: liftM type signature is: 106 | * 107 | * liftM[F[_[_], _], G[_], A](g: G[A]) 108 | * 109 | * Hint Hint Hint: You will have to explicitly specify types for liftM and 110 | * there are convenience type aliases above R_, and W_ above that will help. 111 | * 112 | * liftM[R_, WSV, Unit](???): RWSV[Unit] 113 | * liftM[W_, SV, Unit](???): WSV[Unit] 114 | */ 115 | def httpModify(f: HttpState => HttpState): HttpT[Unit] = 116 | ??? 117 | 118 | /* 119 | * Exercise 8b.7: 120 | * 121 | * Implement get for a Http. 122 | * 123 | * Hint: Try using Http constructor, HttpWriteT tell MonadTrans.liftM (once). 124 | */ 125 | def httpTell(w: HttpWrite): HttpT[Unit] = 126 | ??? 127 | 128 | /* 129 | * Exercise 8b.8: 130 | * 131 | * Implement getBody. 132 | * 133 | * Hint: You may want to use httpAsk. 134 | */ 135 | def getBody: HttpT[String] = 136 | ??? 137 | 138 | /* 139 | * Exercise 8b.9: 140 | * 141 | * Implement addHeader. 142 | * 143 | * Hint: You may want to use httpModify. 144 | */ 145 | def addHeader(name: String, value: String): HttpT[Unit] = 146 | ??? 147 | 148 | /* 149 | * Exercise 8b.10: 150 | * 151 | * Implement log. 152 | * 153 | * Hint: Try using httpTell. 154 | */ 155 | def log(message: String): HttpT[Unit] = 156 | ??? 157 | 158 | implicit def HttpMonad: Monad[HttpT] = 159 | new Monad[HttpT] { 160 | def point[A](a: => A) = HttpT.value(a) 161 | def bind[A, B](a: HttpT[A])(f: A => HttpT[B]) = a flatMap f 162 | } 163 | } 164 | 165 | object HttpTExample { 166 | import Http._ 167 | 168 | /* 169 | * Exercise 8a.11: 170 | * 171 | * Implement echo http service. 172 | * 173 | * Echo service should: 174 | * return body as string, 175 | * add "content-type" header of "text/plain" 176 | * log a message with the length of the body in characters. 177 | * 178 | * Hint: Try using flatMap or for comprehensions. 179 | */ 180 | def echo: HttpT[String] = 181 | ??? 182 | } 183 | -------------------------------------------------------------------------------- /src/main/scala/patterns/HttpValue.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | sealed trait HttpValue[A] { 6 | def fold[X]( 7 | explosion: Throwable => X, 8 | fail: String => X, 9 | ok: A => X 10 | ): X = this match { 11 | case Explosion(exception) => explosion(exception) 12 | case Fail(message) => fail(message) 13 | case Ok(value) => ok(value) 14 | } 15 | 16 | def map[B](f: A => B): HttpValue[B] = 17 | flatMap(f andThen HttpValue.ok) 18 | 19 | def flatMap[B](f: A => HttpValue[B]): HttpValue[B] = 20 | fold(HttpValue.explosion, HttpValue.fail, f) 21 | } 22 | 23 | case class Explosion[A](exception: Throwable) extends HttpValue[A] 24 | case class Fail[A](message: String) extends HttpValue[A] 25 | case class Ok[A](value: A) extends HttpValue[A] 26 | 27 | object HttpValue { 28 | def explosion[A](exception: Throwable): HttpValue[A] = 29 | Explosion(exception) 30 | 31 | def fail[A](message: String): HttpValue[A] = 32 | Fail(message) 33 | 34 | def ok[A](value: A): HttpValue[A] = 35 | Ok(value) 36 | 37 | implicit def HttpValueMonad: Monad[HttpValue] = new Monad[HttpValue] { 38 | def point[A](a: => A) = ok(a) 39 | def bind[A, B](a: HttpValue[A])(f: A => HttpValue[B]) = a flatMap f 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/patterns/Monad.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | trait Monad[F[_]] extends Applicative[F] { 6 | def point[A](a: => A): F[A] 7 | def bind[A, B](a: F[A])(f: A => F[B]): F[B] 8 | 9 | def map[A, B](a: F[A])(f: A => B): F[B] = bind(a)(b => point(f(b))) 10 | 11 | def pure[A](a: => A) = point(a) 12 | def ap[A, B](fa: F[A])(fab: F[A => B]): F[B] = bind(fa)(a => map(fab)(f => f(a))) 13 | } 14 | 15 | object Monad { 16 | def apply[F[_]: Monad]: Monad[F] = 17 | implicitly[Monad[F]] 18 | 19 | implicit def OptionMonad: Monad[Option] = new Monad[Option] { 20 | def point[A](a: => A) = Some(a) 21 | def bind[A, B](a: Option[A])(f: A => Option[B]): Option[B] = a flatMap f 22 | } 23 | 24 | implicit def ListMonad: Monad[List] = new Monad[List] { 25 | def point[A](a: => A) = List(a) 26 | def bind[A, B](a: List[A])(f: A => List[B]): List[B] = a flatMap f 27 | } 28 | } 29 | 30 | 31 | object MonadLaws { 32 | def associative[F[_], A, B, C](fa: F[A], f: A => F[B], g: B => F[C])(implicit FC: Equal[F[C]], F: Monad[F]): Boolean = 33 | FC.equal(F.bind(F.bind(fa)(f))(g), F.bind(fa)((a: A) => F.bind(f(a))(g))) 34 | 35 | def rightIdentity[F[_], A](fa: F[A])(implicit FA: Equal[F[A]], F: Monad[F]): Boolean = 36 | FA.equal(F.bind(fa)(F.point[A](_)), fa) 37 | 38 | def leftIdentity[F[_], A, B](fa: A, f: A => F[B])(implicit FB: Equal[F[B]], F: Monad[F]): Boolean = 39 | FB.equal(F.bind(F.point(fa))(f), f(fa)) 40 | } 41 | 42 | object MonadSyntax { 43 | implicit class AnyMonadSyntax[M[_]: Monad, A](a: M[A]) { 44 | def map[B](f: A => B) = 45 | Monad[M].map(a)(f) 46 | 47 | def flatMap[B](f: A => M[B]) = 48 | Monad[M].bind(a)(f) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/patterns/MonadTrans.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | trait MonadTrans[F[_[_], _]] { 4 | def liftM[G[_]: Monad, A](g: G[A]): F[G, A] 5 | } 6 | 7 | object MonadTrans { 8 | def apply[F[_[_], _]](implicit F: MonadTrans[F]): MonadTrans[F] = 9 | F 10 | 11 | def liftM[F[_[_], _], G[_], A](g: G[A])(implicit F: MonadTrans[F], G: Monad[G]): F[G, A] = 12 | F.liftM(g) 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/patterns/Reader.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | /* 6 | * A reader data type that represents the application of some 7 | * environment to produce a value. 8 | */ 9 | case class Reader[R, A](run: R => A) { 10 | 11 | /* 12 | * Exercise 2.1: 13 | * 14 | * Implement map for Reader[R, A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | * 20 | * Two readers are equal if for all inputs, the same result is produced. 21 | */ 22 | def map[B](f: A => B): Reader[R, B] = 23 | ??? 24 | 25 | /* 26 | * Exercise 2.2: 27 | * 28 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 29 | * 30 | * The following law must hold: 31 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 32 | * 33 | * Two readers are equal if for all inputs, the same result is produced. 34 | */ 35 | def flatMap[B](f: A => Reader[R, B]): Reader[R, B] = 36 | ??? 37 | } 38 | 39 | object Reader { 40 | /* 41 | * Exercise 2.3: 42 | * 43 | * Implement value (a.k.a. return, point, pure). 44 | * 45 | * Hint: Try using Reader constructor. 46 | */ 47 | def value[R, A](a: => A): Reader[R, A] = 48 | ??? 49 | 50 | /* 51 | * Exercise 2.4: 52 | * 53 | * Implement ask. 54 | * 55 | * Ask provides access to the current environment (R). 56 | * 57 | * Hint: Try using Reader constructor. 58 | */ 59 | def ask[R]: Reader[R, R] = 60 | ??? 61 | 62 | /* 63 | * Exercise 2.5: 64 | * 65 | * Implement local. 66 | * 67 | * Local produce a reader that runs with a modified environment. 68 | * 69 | * Hint: Try using Reader constructor. 70 | */ 71 | def local[R, A](f: R => R)(reader: Reader[R, A]): Reader[R, A] = 72 | ??? 73 | 74 | /* 75 | * Exercise 2.6: 76 | * 77 | * Sequence, a list of Readers, to a Reader of Lists. 78 | */ 79 | def sequence[R, A](readers: List[Reader[R, A]]): Reader[R, List[A]] = 80 | ??? 81 | 82 | implicit def ReaderMonoid[R, A: Monoid]: Monoid[Reader[R, A]] = 83 | new Monoid[Reader[R, A]] { 84 | def identity: Reader[R, A] = 85 | value[R, A](Monoid[A].identity) 86 | 87 | def op(a: Reader[R, A], b: Reader[R, A]) = 88 | for { aa <- a; bb <- b } yield Monoid[A].op(aa, bb) 89 | } 90 | 91 | 92 | class Reader_[R] { 93 | type l[a] = Reader[R, a] 94 | } 95 | 96 | implicit def ReaderMonad[R]: Monad[Reader_[R]#l] = 97 | new Monad[Reader_[R]#l] { 98 | def point[A](a: => A): Reader[R, A] = 99 | value(a) 100 | 101 | def bind[A, B](r: Reader[R, A])(f: A => Reader[R, B]) = 102 | r flatMap f 103 | } 104 | } 105 | 106 | 107 | /* 108 | * *Challenge* Exercise 2.7: Indirection. 109 | * 110 | * Lookup a specified config value, and then use its values 111 | * as keys to look up a subsequent set of values. 112 | * 113 | * Complete the implementation, some of the methods are provided 114 | * fill in the remainder, to complete the spec. 115 | */ 116 | object Example { 117 | case class ConfigEntry(name: String, values: List[String]) 118 | case class Config(data: List[ConfigEntry]) 119 | 120 | /* 121 | * For a single name, lookup all of the direct values for that name. 122 | * 123 | * Libraries available: 124 | * - The Reader.* libraries 125 | * - List[A] has `find` method that will provide a Option[A] 126 | * - Option[A] has a `getOrElse` method similar to challenge1.Result 127 | * 128 | * Hint: Starting with Reader.ask will help. 129 | */ 130 | def direct(name: String): Reader[Config, List[String]] = 131 | ??? 132 | 133 | /* 134 | * For a single name, lookup all of the indirect values, that 135 | * is those values whose key is a one of the direct values of 136 | * the specified name. 137 | * 138 | * Libraries available: 139 | * - List[List[A]].flatten will produce a List[A]. 140 | * 141 | * Hint: Starting with Reader.sequence will be important. 142 | */ 143 | def indirect(name: String): Reader[Config, List[String]] = 144 | ??? 145 | } 146 | -------------------------------------------------------------------------------- /src/main/scala/patterns/ReaderT.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | /* 6 | * A reader data type in transformer form that represents the application of some 7 | * environment to produce a value in some effect M. 8 | */ 9 | case class ReaderT[M[_], R, A](run: R => M[A]) { 10 | 11 | /* 12 | * Exercise 5.1: 13 | * 14 | * Implement map for ReaderT[M, R, A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | * 20 | */ 21 | def map[B](f: A => B)(implicit M: Monad[M]): ReaderT[M, R, B] = 22 | ??? 23 | 24 | /* 25 | * Exercise 5.2: 26 | * 27 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 28 | * 29 | * The following law must hold: 30 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 31 | * 32 | */ 33 | def flatMap[B](f: A => ReaderT[M, R, B])(implicit M: Monad[M]): ReaderT[M, R, B] = 34 | ??? 35 | } 36 | 37 | object ReaderT { 38 | /* 39 | * Exercise 5.3: 40 | * 41 | * Implement value (a.k.a. return, point, pure). 42 | * 43 | * Hint: Try using ReaderT constructor. 44 | */ 45 | def value[M[_]: Monad, R, A](a: => A): ReaderT[M, R, A] = 46 | ??? 47 | 48 | /* 49 | * Exercise 5.4: 50 | * 51 | * Implement ask. 52 | * 53 | * Ask provides access to the current environment (R). 54 | * 55 | * Hint: Try using ReaderT constructor. 56 | */ 57 | def ask[M[_]: Monad, R]: ReaderT[M, R, R] = 58 | ??? 59 | 60 | /* 61 | * Exercise 5.5: 62 | * 63 | * Implement local. 64 | * 65 | * Local produce a reader that runs with a modified environment. 66 | * 67 | * Hint: Try using ReaderT constructor. 68 | */ 69 | def local[M[_], R, A](f: R => R)(reader: ReaderT[M, R, A]): ReaderT[M, R, A] = 70 | ??? 71 | 72 | class ReaderT_[F[_], R] { 73 | type l[a] = ReaderT[F, R, a] 74 | } 75 | 76 | class ReaderT__[R] { 77 | type l[f[_], a] = ReaderT[f, R, a] 78 | } 79 | 80 | implicit def ReaderTMonad[F[_], R](implicit F: Monad[F]): Monad[ReaderT_[F, R]#l] = 81 | new Monad[ReaderT_[F, R]#l] { 82 | def point[A](a: => A) = ReaderT(_ => F.point(a)) 83 | def bind[A, B](a: ReaderT[F, R, A])(f: A => ReaderT[F, R, B]) = a flatMap f 84 | } 85 | 86 | /* 87 | * Exercise 5.6: 88 | * 89 | * Implement monad trans instance. 90 | * 91 | * Hint: Try using ReaderT constructor. 92 | */ 93 | implicit def ReaderTMonadTrans[R]: MonadTrans[ReaderT__[R]#l] = new MonadTrans[ReaderT__[R]#l] { 94 | def liftM[M[_]: Monad, A](ga: M[A]): ReaderT[M, R, A] = 95 | ??? 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/scala/patterns/State.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | /* 6 | * A state data type that represents the threading 7 | * of some state value through computations. 8 | */ 9 | case class State[S, A](run: S => (S, A)) { 10 | 11 | /* 12 | * Exercise 4.1: 13 | * 14 | * Implement map for State[S, A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | * 20 | */ 21 | def map[B](f: A => B): State[S, B] = 22 | ??? 23 | 24 | /* 25 | * Exercise 4.2: 26 | * 27 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 28 | * 29 | * The following law must hold: 30 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 31 | * 32 | */ 33 | def flatMap[B](f: A => State[S, B]): State[S, B] = 34 | ??? 35 | } 36 | 37 | object State { 38 | /* 39 | * Exercise 4.3: 40 | * 41 | * Implement value (a.k.a. return, point, pure). 42 | * 43 | * Hint: Try using State constructor. 44 | */ 45 | def value[S, A](a: => A): State[S, A] = 46 | ??? 47 | 48 | /* 49 | * Exercise 4.4: 50 | * 51 | * Implement get. 52 | * 53 | * Get provides access to the current state (S). 54 | * 55 | * Hint: Try using State constructor. 56 | */ 57 | def get[S]: State[S, S] = 58 | ??? 59 | 60 | /* 61 | * Exercise 4.5: 62 | * 63 | * Implement gets. 64 | * 65 | * Gets provides access to a view of the current state (S). 66 | * 67 | * Hint: Try building on get. 68 | */ 69 | def gets[S, A](f: S => A): State[S, A] = 70 | ??? 71 | 72 | /* 73 | * Exercise 4.6: 74 | * 75 | * Implement modify. 76 | * 77 | * Update the current state and produce no value. 78 | * 79 | * Hint: Try using State constructor. 80 | */ 81 | def modify[S](f: S => S): State[S, Unit] = 82 | ??? 83 | 84 | /* 85 | * Exercise 4.7: 86 | * 87 | * Implement put. 88 | * 89 | * Clobber the current state and produce no value. 90 | * 91 | * Hint: Try building on modify. 92 | */ 93 | def put[S](s: S): State[S, Unit] = 94 | ??? 95 | 96 | class State_[S] { 97 | type l[a] = State[S, a] 98 | } 99 | 100 | implicit def StateMonad[S]: Monad[State_[S]#l] = 101 | new Monad[State_[S]#l] { 102 | def point[A](a: => A) = value(a) 103 | def bind[A, B](a: State[S, A])(f: A => State[S, B]) = a flatMap f 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/scala/patterns/StateT.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._ 4 | 5 | /* 6 | * A state data type that represents the threading 7 | * of some state value through computations in some effect M. 8 | */ 9 | case class StateT[M[_], S, A](run: S => M[(S, A)]) { 10 | 11 | /* 12 | * Exercise 7.1: 13 | * 14 | * Implement map for StateT[M, S, A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | * 20 | */ 21 | def map[B](f: A => B)(implicit M: Monad[M]): StateT[M, S, B] = 22 | ??? 23 | 24 | /* 25 | * Exercise 7.2: 26 | * 27 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 28 | * 29 | * The following law must hold: 30 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 31 | * 32 | */ 33 | def flatMap[B](f: A => StateT[M, S, B])(implicit M: Monad[M]): StateT[M, S, B] = 34 | ??? 35 | } 36 | 37 | object StateT { 38 | /* 39 | * Exercise 7.3: 40 | * 41 | * Implement value (a.k.a. return, point, pure). 42 | * 43 | * Hint: Try using StateT constructor. 44 | */ 45 | def value[M[_]: Monad, S, A](a: => A): StateT[M, S, A] = 46 | ??? 47 | 48 | /* 49 | * Exercise 7.4: 50 | * 51 | * Implement get. 52 | * 53 | * Get provides access to the current state (S). 54 | * 55 | * Hint: Try using StateT constructor. 56 | */ 57 | def get[M[_]: Monad, S]: StateT[M, S, S] = 58 | ??? 59 | 60 | /* 61 | * Exercise 7.5: 62 | * 63 | * Implement gets. 64 | * 65 | * Gets provides access to a view of the current state (S). 66 | * 67 | * Hint: Try building on get. 68 | */ 69 | def gets[M[_]: Monad, S, A](f: S => A): StateT[M, S, A] = 70 | ??? 71 | 72 | /* 73 | * Exercise 7.6: 74 | * 75 | * Implement modify. 76 | * 77 | * Update the current state and produce no value. 78 | * 79 | * Hint: Try using State constructor. 80 | */ 81 | def modify[M[_]: Monad, S](f: S => S): StateT[M, S, Unit] = 82 | ??? 83 | 84 | /* 85 | * Exercise 7.7: 86 | * 87 | * Implement put. 88 | * 89 | * Clobber the current state and produce no value. 90 | * 91 | * Hint: Try building on modify. 92 | */ 93 | def put[M[_]: Monad, S](s: S): StateT[M, S, Unit] = 94 | ??? 95 | 96 | class StateT_[F[_], S] { 97 | type l[a] = StateT[F, S, a] 98 | } 99 | 100 | class StateT__[S] { 101 | type l[f[_], a] = StateT[f, S, a] 102 | } 103 | 104 | implicit def StateTMonad[F[_], S](implicit F: Monad[F]): Monad[StateT_[F, S]#l] = 105 | new Monad[StateT_[F, S]#l] { 106 | def point[A](a: => A) = StateT(s => F.point((s, a))) 107 | def bind[A, B](a: StateT[F, S, A])(f: A => StateT[F, S, B]) = a flatMap f 108 | } 109 | 110 | /* 111 | * Exercise 7.8: 112 | * 113 | * Implement monad trans instance. 114 | * 115 | * Hint: Try using StateT constructor and Monad[M].map(ga). 116 | */ 117 | implicit def StateTMonadTrans[S]: MonadTrans[StateT__[S]#l] = 118 | ??? 119 | } 120 | -------------------------------------------------------------------------------- /src/main/scala/patterns/TypeLambda.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | class TypeLambda[F[_, _], A] { 4 | type l[a] = F[A, a] 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/patterns/Writer.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._, EqualSyntax._ 4 | 5 | /* 6 | * A writer data type that represents the pair of some 7 | * writer content with the production of a value. 8 | */ 9 | case class Writer[W, A](log: W, value: A) { 10 | 11 | def run: (W, A) = 12 | (log, value) 13 | 14 | /* 15 | * Exercise 3.1: 16 | * 17 | * Implement map for Writer[W, A]. 18 | * 19 | * The following laws must hold: 20 | * 1) r.map(z => z) == r 21 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 22 | * 23 | */ 24 | def map[B](f: A => B): Writer[W, B] = 25 | ??? 26 | 27 | /* 28 | * Exercise 3.2: 29 | * 30 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 31 | * 32 | * The following law must hold: 33 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 34 | * 35 | */ 36 | def flatMap[B](f: A => Writer[W, B])(implicit M: Monoid[W]): Writer[W, B] = 37 | ??? 38 | } 39 | 40 | object Writer { 41 | /* 42 | * Exercise 3.3: 43 | * 44 | * Implement value (a.k.a. return, point, pure) given a 45 | * Monoid for W. 46 | * 47 | * Hint: Try using Writer constructor. 48 | */ 49 | def value[W: Monoid, A](a: A): Writer[W, A] = 50 | ??? 51 | 52 | /* 53 | * Exercise 3.4: 54 | * 55 | * Implement tell. 56 | * 57 | * Tell appends the writer content w and produces no value. 58 | * 59 | * Hint: Try using Writer constructor. 60 | */ 61 | def tell[W](w: W): Writer[W, Unit] = 62 | ??? 63 | 64 | /* 65 | * Exercise 3.5: 66 | * 67 | * Sequence, a list of Readers, to a Reader of Lists. 68 | */ 69 | def sequence[W: Monoid, A](writers: List[Writer[W, A]]): Writer[W, List[A]] = 70 | ??? 71 | 72 | class Writer_[W] { 73 | type l[a] = Writer[W, a] 74 | } 75 | 76 | implicit def WriterMonad[W: Monoid]: Monad[Writer_[W]#l] = 77 | new Monad[Writer_[W]#l] { 78 | def point[A](a: => A) = value[W, A](a) 79 | def bind[A, B](a: Writer[W, A])(f: A => Writer[W, B]) = a flatMap f 80 | } 81 | 82 | implicit def WriterEqual[W: Equal, A: Equal] = 83 | Equal.from[Writer[W, A]]((a, b) => (a.log -> a.value) === (b.log -> b.value)) 84 | 85 | implicit def WriterMoniod[W: Monoid, A: Monoid]: Monoid[Writer[W, A]] = 86 | new Monoid[Writer[W, A]] { 87 | def identity = Writer.value[W, A](Monoid[A].identity) 88 | def op(l: Writer[W, A], r: Writer[W, A]) = 89 | Writer(Monoid[W].op(l.log, r.log), Monoid[A].op(l.value, r.value)) 90 | } 91 | } 92 | 93 | /* 94 | * *Challenge* Exercise 3.6: Stocks + Stats. 95 | * 96 | * We have some stock prices over time, and we make a simple 97 | * adjustment: 98 | * Map across each ticker price and do an adjustment by adding 99 | * 1000 cents to every value under 10000 and 10 cents to every 100 | * value equal to or over 10000. 101 | * 102 | * However, while we compute this answer we also want to caculate 103 | * summary statistics for our data, specifically, min, max, total, 104 | * and count. 105 | * 106 | * Use the Writer data type to compute stats whilst we calculate 107 | * our adjustments. 108 | * 109 | * Complete the implementation, some of the methods are provided 110 | * fill in the remainder, to complete the spec. 111 | */ 112 | object WriterExample { 113 | case class Stats(min: Int, max: Int, total: Int, count: Int) 114 | case class Stock(ticker: String, date: String, cents: Int) 115 | 116 | /** 117 | * Implement our algorthim. 118 | * 119 | * Hint: Writer(W, A) and Writer.sequence will be useful here. 120 | */ 121 | def stocks(data: List[Stock]): (Stats, List[Stock]) = 122 | ??? 123 | 124 | /** 125 | * A monoid for Stats. 126 | */ 127 | implicit def StatsMonoid: Monoid[Stats] = 128 | new Monoid[Stats] { 129 | def identity = 130 | ??? 131 | def op(l: Stats, r: Stats) = 132 | ??? 133 | } 134 | 135 | def exampledata = List( 136 | Stock("FAKE", "2012-01-01", 10000) 137 | , Stock("FAKE", "2012-01-02", 10020) 138 | , Stock("FAKE", "2012-01-03", 10022) 139 | , Stock("FAKE", "2012-01-04", 10005) 140 | , Stock("FAKE", "2012-01-05", 9911) 141 | , Stock("FAKE", "2012-01-06", 6023) 142 | , Stock("FAKE", "2012-01-07", 7019) 143 | , Stock("CAKE", "2012-01-01", 1) 144 | , Stock("CAKE", "2012-01-02", 2) 145 | , Stock("CAKE", "2012-01-03", 3) 146 | , Stock("CAKE", "2012-01-04", 4) 147 | , Stock("CAKE", "2012-01-05", 5) 148 | , Stock("CAKE", "2012-01-06", 6) 149 | , Stock("CAKE", "2012-01-07", 7) 150 | , Stock("BAKE", "2012-01-01", 99999) 151 | , Stock("BAKE", "2012-01-02", 99999) 152 | , Stock("BAKE", "2012-01-03", 99999) 153 | , Stock("BAKE", "2012-01-04", 99999) 154 | , Stock("BAKE", "2012-01-05", 99999) 155 | , Stock("BAKE", "2012-01-06", 99999) 156 | , Stock("BAKE", "2012-01-07", 99999) 157 | , Stock("LAKE", "2012-01-01", 10012) 158 | , Stock("LAKE", "2012-01-02", 7000) 159 | , Stock("LAKE", "2012-01-03", 1234) 160 | , Stock("LAKE", "2012-01-04", 10) 161 | , Stock("LAKE", "2012-01-05", 6000) 162 | , Stock("LAKE", "2012-01-06", 6099) 163 | , Stock("LAKE", "2012-01-07", 5999) 164 | ) 165 | } 166 | -------------------------------------------------------------------------------- /src/main/scala/patterns/WriterT.scala: -------------------------------------------------------------------------------- 1 | package patterns 2 | 3 | import intro._, EqualSyntax._ 4 | 5 | /* 6 | * A writer data type in transformer form that represents the pair of some 7 | * writer content with the production of a value in some effect M. 8 | */ 9 | case class WriterT[M[_], W, A](run: M[(W, A)]) { 10 | 11 | /* 12 | * Exercise 6.1: 13 | * 14 | * Implement map for WriterT[M, W, A]. 15 | * 16 | * The following laws must hold: 17 | * 1) r.map(z => z) == r 18 | * 2) r.map(z => f(g(z))) == r.map(g).map(f) 19 | * 20 | */ 21 | def map[B](f: A => B)(implicit W: Monoid[W], M: Monad[M]): WriterT[M, W, B] = 22 | ??? 23 | 24 | /* 25 | * Exercise 6.2: 26 | * 27 | * Implement flatMap (a.k.a. bind, a.k.a. >>=). 28 | * 29 | * The following law must hold: 30 | * r.flatMap(f).flatMap(g) == r.flatMap(z => f(z).flatMap(g)) 31 | * 32 | */ 33 | def flatMap[B](f: A => WriterT[M, W, B])(implicit W: Monoid[W], M: Monad[M]): WriterT[M, W, B] = 34 | ??? 35 | } 36 | 37 | object WriterT { 38 | /* 39 | * Exercise 6.3: 40 | * 41 | * Implement a convenience for constructing a WriterT with 42 | * value and writer content. 43 | * 44 | */ 45 | def writer[M[_]: Monad, W, A](a: A)(w: W): WriterT[M, W, A] = 46 | ??? 47 | 48 | /* 49 | * Exercise 6.4: 50 | * 51 | * Implement value (a.k.a. return, point, pure) given a 52 | * Monoid for W. 53 | */ 54 | def value[M[_]: Monad, W: Monoid, A](a: => A): WriterT[M, W, A] = 55 | ??? 56 | 57 | /* 58 | * Exercise 6.5: 59 | * 60 | * Implement tell. 61 | * 62 | * Tell appends the writer content w and produces no value. 63 | */ 64 | def tell[M[_]: Monad, W](w: W): WriterT[M, W, Unit] = 65 | ??? 66 | 67 | 68 | class WriterT_[M[_], W] { 69 | type l[a] = WriterT[M, W, a] 70 | } 71 | 72 | class WriterT__[W] { 73 | type l[f[_], a] = WriterT[f, W, a] 74 | } 75 | 76 | implicit def WriterTMonad[F[_], W](implicit F: Monad[F], W: Monoid[W]): Monad[WriterT_[F, W]#l] = 77 | new Monad[WriterT_[F, W]#l] { 78 | def point[A](a: => A) = WriterT(F.point((W.identity, a))) 79 | def bind[A, B](a: WriterT[F, W, A])(f: A => WriterT[F, W, B]) = a flatMap f 80 | } 81 | 82 | implicit def WriterTEqual[F[_], W, A](implicit E: Equal[F[(W, A)]]) = 83 | Equal.from[WriterT[F, W, A]]((a, b) => a.run === b.run) 84 | 85 | /* 86 | * Exercise 6.6: 87 | * 88 | * Implement monad trans instance. 89 | * 90 | * Hint: Try using WriterT constructor and Monad[M].map(ga). 91 | */ 92 | implicit def WriterTMonadTrans[W:Monoid]: MonadTrans[WriterT__[W]#l] = 93 | ??? 94 | } 95 | -------------------------------------------------------------------------------- /src/test/scala/intro/ListSpecification.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | import org.scalacheck._, Arbitrary._, Gen._, Prop._ 4 | 5 | object ListSpecification extends Properties("List") { 6 | /* 7 | * Example: Verify that that our Lists.length matches the 8 | * builtin List#size 9 | */ 10 | property("Lists#length matches standard library") = 11 | forAll((xs: List[Int]) => Lists.length(xs) == xs.size) 12 | 13 | /* Exercise 1 */ 14 | property("Lists#append - the length of the result is equal to the sum of the two input lengths") = 15 | ??? 16 | 17 | /* Exercise 2 */ 18 | property("Lists#append - every element in the first and second list appears in the result") = 19 | ??? 20 | 21 | /* Exercise 3 */ 22 | property("Lists#filter - filter(_ => false) always gives empty list") = 23 | ??? 24 | 25 | /* Exercise 4 */ 26 | property("Lists#filter - filter(_ => true) always gives input list") = 27 | ??? 28 | 29 | /* Exercise 5 */ 30 | property("Lists#filter - length of output is always less than length of input") = 31 | ??? 32 | 33 | /* *Challenge* exercise 6 34 | Identify a set of properties that together with the type signature 35 | guarantees the validity of your reverse function (assuming pure-total FP) */ 36 | property("Lists#reverse...") = 37 | ??? 38 | 39 | /* *Challenge* exercise 7 40 | Identify a set of properties for testing sequence */ 41 | property("Lists#sequence...") = 42 | ??? 43 | 44 | /* *Challenge* exercise 8 45 | Identify a set of properties for testing ranges */ 46 | property("Lists#ranges...") = 47 | ??? 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/intro/MonoidSpecification.scala: -------------------------------------------------------------------------------- 1 | package intro 2 | 3 | import org.scalacheck._, Arbitrary._, Gen._, Prop._ 4 | 5 | object MonoidSpecification extends Properties("Monoid") { 6 | /* 7 | * Exercise 1: Define arbitrary instances for these basic monoid data types. 8 | */ 9 | 10 | implicit def MinArbitrary: Arbitrary[Min] = 11 | Arbitrary(arbitrary[Int].map(n => Min(n))) 12 | 13 | implicit def MaxArbitrary: Arbitrary[Max] = 14 | ??? 15 | 16 | implicit def FirstArbitrary: Arbitrary[First[Int]] = 17 | ??? 18 | 19 | /* 20 | * *Challenge* Exercise 2: Define arbitrary instance for Endo. 21 | * 22 | * Hint: Use Gen.choose(n: Int, m: Int): Gen[Int] 23 | * Hint: Use Gen.oneOf[A](values: A*): Gen[A] 24 | */ 25 | implicit def EndoArbitrary: Arbitrary[Endo[Int]] = 26 | ??? 27 | 28 | /* 29 | * *Challenge* Exercise 2: ensure that our Monoid instances satisfy 30 | * the monoid laws. 31 | * 32 | * (A subset has been specified to keep things smaller) 33 | * 34 | * Note: Try refactoring as you go, don't repeat the test for 35 | * every property. 36 | */ 37 | 38 | property("Min is Lawful") = 39 | ??? 40 | 41 | property("Max is Lawful") = 42 | ??? 43 | 44 | property("Endo is Lawful") = 45 | ??? 46 | 47 | property("First is Lawful") = 48 | ??? 49 | 50 | property("List is Lawful") = 51 | ??? 52 | 53 | property("Map is Lawful") = 54 | ??? 55 | 56 | 57 | /** 58 | * *Challenge* Exercise 3: Can you use a property to prove something isn't a Monoid? 59 | * 60 | * Hint: What about doubles? 61 | */ 62 | property("Not a monoid") = 63 | ??? 64 | 65 | } 66 | --------------------------------------------------------------------------------