init
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.jar filter=lfs diff=lfs merge=lfs -text
|
||||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,2 +1,18 @@
|
|||||||
*.class
|
lib_managed
|
||||||
*.log
|
project/boot
|
||||||
|
project/build/target
|
||||||
|
scala-koans.iws
|
||||||
|
scala-koans.ipr
|
||||||
|
scala-koans.iml
|
||||||
|
target
|
||||||
|
.*\.DS_Store
|
||||||
|
.*\.zip
|
||||||
|
.idea*
|
||||||
|
.ctags
|
||||||
|
.vimrc
|
||||||
|
tags
|
||||||
|
.*\.swp
|
||||||
|
project/plugins/target
|
||||||
|
project/plugins/lib_managed
|
||||||
|
project/plugins/src_managed
|
||||||
|
.*\.orig
|
||||||
|
|||||||
6
.hg_archival.txt
Normal file
6
.hg_archival.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
repo: e433542a2a0d9eb9fbe1b548ca5c76d54d12cb4e
|
||||||
|
node: 769e22d30ec694417d909efedabe480b8c37d392
|
||||||
|
branch: default
|
||||||
|
latesttag: null
|
||||||
|
latesttagdistance: 138
|
||||||
|
changessincelatesttag: 143
|
||||||
18
.hgignore
Normal file
18
.hgignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
lib_managed
|
||||||
|
project/boot
|
||||||
|
project/build/target
|
||||||
|
scala-koans.iws
|
||||||
|
scala-koans.ipr
|
||||||
|
scala-koans.iml
|
||||||
|
target
|
||||||
|
.*\.DS_Store
|
||||||
|
.*\.zip
|
||||||
|
.idea*
|
||||||
|
.ctags
|
||||||
|
.vimrc
|
||||||
|
tags
|
||||||
|
.*\.swp
|
||||||
|
project/plugins/target
|
||||||
|
project/plugins/lib_managed
|
||||||
|
project/plugins/src_managed
|
||||||
|
.*\.orig
|
||||||
30
ListOfKoans.txt
Normal file
30
ListOfKoans.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
AboutValAndVar.scala
|
||||||
|
AboutConstructors.scala
|
||||||
|
|
||||||
|
AboutForExpressions.scala
|
||||||
|
AboutNamedAndDefaultArguments.scala
|
||||||
|
|
||||||
|
AboutTuples.scala
|
||||||
|
AboutLists.scala
|
||||||
|
AboutMaps.scala
|
||||||
|
AboutSets.scala
|
||||||
|
AboutMutableMaps.scala
|
||||||
|
AboutMutableSets.scala
|
||||||
|
AboutSequencesAndArrays.scala
|
||||||
|
|
||||||
|
AboutPatternMatching.scala
|
||||||
|
AboutCaseClasses.scala
|
||||||
|
AboutOptions.scala
|
||||||
|
AboutEmptyValues.scala
|
||||||
|
AboutParentClasses.scala
|
||||||
|
|
||||||
|
AboutAccessModifiers.scala
|
||||||
|
AboutImportsAndPackages.scala
|
||||||
|
|
||||||
|
AboutLazySequences.scala
|
||||||
|
AboutHigherOrderFunctions.scala
|
||||||
|
|
||||||
|
AboutPreconditions.scala
|
||||||
|
AboutTraits.scala
|
||||||
|
AboutTypeSignatures.scala
|
||||||
|
AboutUniformAccessPrinciple.scala
|
||||||
1
README-scala.txt
Normal file
1
README-scala.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Please see http://bitbucket.org/dickwall/scala-koans/wiki/Home for more details on scala-koans
|
||||||
7
build.sbt
Normal file
7
build.sbt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name := "Scala Koans"
|
||||||
|
|
||||||
|
version := "1.0"
|
||||||
|
|
||||||
|
scalaVersion := "2.9.0-1"
|
||||||
|
|
||||||
|
libraryDependencies ++= Seq("junit" % "junit" % "4.8" % "test", "org.scalatest" % "scalatest_2.9.0" % "1.6.1" % "test")
|
||||||
51
ideaboard.txt
Normal file
51
ideaboard.txt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
Putting these in order because some koan packages are prerequisites of others.
|
||||||
|
Feel free to put it in logical order so that
|
||||||
|
|
||||||
|
|
||||||
|
Test asserts - Message, bool, etc. (DMarsh: done)
|
||||||
|
Nothing values (DMarsh: done)
|
||||||
|
uniform access principle (DMarsh: Done)
|
||||||
|
named/default args (DMarsh: done)
|
||||||
|
Tuples - syntax (DMarsh: done)
|
||||||
|
Pattern Matching (DMarsh: done ?? Should we add more)
|
||||||
|
Preconditions (DMarsh: Done)
|
||||||
|
Functions returning functions
|
||||||
|
Functions taking functions
|
||||||
|
if expressions
|
||||||
|
for comprehensions (DMarsh: done)
|
||||||
|
imports, packages, and visibility (dhinojosa:In Process)
|
||||||
|
classes (dhinojosa:In Process)
|
||||||
|
mutation
|
||||||
|
lists / cons
|
||||||
|
collections array, list (DMarsh: Done, also maps and mutable maps, sets and mutable sets)
|
||||||
|
map, reduce, filter (DMarsh: Done ?? covered in various collection classes)
|
||||||
|
immutability/side effects (DMarsh: done ?? Do we have enough here??)
|
||||||
|
lazy sequences (Nilanjan)
|
||||||
|
recursion
|
||||||
|
currying / pfa
|
||||||
|
case classes (DMarsh: Done)
|
||||||
|
Pattern Matching
|
||||||
|
traits / mixins (dhinojosa: In Process)
|
||||||
|
type signatures (dhinojosa: In Process)
|
||||||
|
state identity lifetime
|
||||||
|
memoization
|
||||||
|
recursive list processing
|
||||||
|
lambda
|
||||||
|
null option types (Nilanjan)
|
||||||
|
reflection
|
||||||
|
scala properties - set date / use earlier date
|
||||||
|
|
||||||
|
|
||||||
|
Make sure this is covered:
|
||||||
|
scala> def pointit() = 123
|
||||||
|
pointit: ()Int
|
||||||
|
|
||||||
|
scala> () => pointit()
|
||||||
|
res2: () => Int = <function0>
|
||||||
|
|
||||||
|
scala> res2.apply
|
||||||
|
res4: Int = 123
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
13
project/plugins/build.sbt
Normal file
13
project/plugins/build.sbt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
resolvers += "sbt-idea-repo" at "http://mpeltonen.github.com/maven/"
|
||||||
|
|
||||||
|
libraryDependencies += "com.github.mpeltonen" %% "sbt-idea" % "0.10.0"
|
||||||
|
|
||||||
|
resolvers += {
|
||||||
|
val typesafeRepoUrl = new java.net.URL("http://repo.typesafe.com/typesafe/releases")
|
||||||
|
val pattern = Patterns(false, "[organisation]/[module]/[sbtversion]/[revision]/[type]s/[module](-[classifier])-[revision].[ext]")
|
||||||
|
Resolver.url("Typesafe Repository", typesafeRepoUrl)(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
libraryDependencies <<= (libraryDependencies, sbtVersion) { (deps, version) =>
|
||||||
|
deps :+ ("com.typesafe.sbteclipse" %% "sbteclipse" % "1.3-RC1" extra("sbtversion" -> version))
|
||||||
|
}
|
||||||
3
sbt-launch.jar
Normal file
3
sbt-launch.jar
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b8af85f4ecc3d07a04fb35a9e056a95de2dd9fe260a1551ba9aca5d50b8d1010
|
||||||
|
size 937683
|
||||||
2
sbt.bat
Normal file
2
sbt.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
set SCRIPT_DIR=%~dp0
|
||||||
|
java -Xmx512M -jar "%SCRIPT_DIR%sbt-launch.jar" %*
|
||||||
9
src/main/scala/org/functionalkoans/App.scala
Normal file
9
src/main/scala/org/functionalkoans/App.scala
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package org.functionalkoans
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hello world!
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
object App extends scala.App {
|
||||||
|
println( "Hello World!" )
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.functionalkoans.forscala;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 5/11/11
|
||||||
|
* Time: 10:55 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
public class SomeJavaClass {
|
||||||
|
public int findSizeOfRawType(List list) {
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findSizeOfUnknownType(List<?> list) {
|
||||||
|
return list.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/test/resources/TODO.txt
Normal file
43
src/test/resources/TODO.txt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
type members
|
||||||
|
self types
|
||||||
|
apply/unapply extractor methods
|
||||||
|
equality
|
||||||
|
case class inheritance
|
||||||
|
objects?
|
||||||
|
inner classes
|
||||||
|
while if control structures
|
||||||
|
for loop and for loop comprehension
|
||||||
|
DSLs
|
||||||
|
putting a method in to a function
|
||||||
|
CanBuildFrom newBuilder
|
||||||
|
@bridge @tailrec and other notations
|
||||||
|
sealed traits and object
|
||||||
|
associativity? is that covered? I believe it was.
|
||||||
|
uses of with keyword there are some various reasons to use em.
|
||||||
|
new Object with {}
|
||||||
|
structural types
|
||||||
|
existential
|
||||||
|
higher kinded
|
||||||
|
|
||||||
|
def (x : => Int) = x()
|
||||||
|
|
||||||
|
shorthand implicit def
|
||||||
|
|
||||||
|
class A {
|
||||||
|
type Abstract
|
||||||
|
def method: Abstract
|
||||||
|
}
|
||||||
|
class B extends A {
|
||||||
|
type Abstract <: Seq[Int] // still abstract but implementation of method must return a Seq[Int] or subtype
|
||||||
|
}
|
||||||
|
class C extends A {
|
||||||
|
type Abstract = List[Int]
|
||||||
|
def method = Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
object OtherUses {
|
||||||
|
type X = java.io.File
|
||||||
|
println(new X("/"))
|
||||||
|
type Y[T] = Se1q[T] // Y[Int] = Seq[Int], Y[String] = Seq[String], etc.
|
||||||
|
type FromStringTo[T] = String=>T // FromStringTo[Int] = String=>Int; FromStringToT[_] = String => _
|
||||||
|
def methodWithContextBound[A : FromStringTo](x: A) = null // there must a an implicit String=>A in scope
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutAbstractTypes extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("A type can be declared anywhere") {
|
||||||
|
type G = Int
|
||||||
|
val x: G = 3
|
||||||
|
val y: G = 5
|
||||||
|
(x + y) should be(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Abstract types are types that are declared in an abstract class, and
|
||||||
|
| since it is in abstract class they don't need to be defined until
|
||||||
|
| they are extended.""") {
|
||||||
|
|
||||||
|
abstract class Generator {
|
||||||
|
type F
|
||||||
|
|
||||||
|
def generate: F
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringGenerator extends Generator {
|
||||||
|
type F = String
|
||||||
|
|
||||||
|
def generate = "Scooters are fun"
|
||||||
|
}
|
||||||
|
|
||||||
|
new StringGenerator().generate should be("Scooters are fun")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Abstract types can also be included in a trait") {
|
||||||
|
trait Counter {
|
||||||
|
type C
|
||||||
|
def count(c: C): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class CharacterCounter extends Counter {
|
||||||
|
type C = String
|
||||||
|
override def count(c: String) = c.size
|
||||||
|
}
|
||||||
|
|
||||||
|
new CharacterCounter().count("Boolean") should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Abstract types are also by default public, the can be locked down with an
|
||||||
|
| access modifier""") {
|
||||||
|
trait Counter {
|
||||||
|
protected type C
|
||||||
|
def count(c: C): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class CharacterCounter extends Counter {
|
||||||
|
protected type C = String
|
||||||
|
override def count(c: String) = c.size
|
||||||
|
}
|
||||||
|
|
||||||
|
new CharacterCounter().count("Awesome") should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Perhaps the best reason for abstract types is the
|
||||||
|
| ability to refine types so it can make sense. The example often used
|
||||||
|
| is creating an Animal class, and refining the
|
||||||
|
| type of Food that can be eaten. This koan will also show use of a type bound
|
||||||
|
| as an abstract type""") {
|
||||||
|
|
||||||
|
trait Food
|
||||||
|
class DogFood extends Food {
|
||||||
|
override def toString = "Dog Food"
|
||||||
|
}
|
||||||
|
class Hay extends Food {
|
||||||
|
override def toString = "Hay"
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Animal {
|
||||||
|
type C <: Food //Animal has to eat some sort of food, depends on animal
|
||||||
|
def feedMe(c:C)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cow {
|
||||||
|
type C = Hay //A good choice
|
||||||
|
def feedMe(c:C) = "Nom Nom, I am eating " + c
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dog {
|
||||||
|
type C = DogFood //Again, a good choice
|
||||||
|
def feedMe(c:C) = "Nom Nom, I am eating " + c
|
||||||
|
}
|
||||||
|
|
||||||
|
//new Cow().feedMe(new DogFood) this wont compile, because it's not right
|
||||||
|
new Cow().feedMe(new Hay()) should be ("Nom Nom, I am eating Hay")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Abstract Types can be any type even a type with type parameter""") {
|
||||||
|
trait Counter {
|
||||||
|
type T <: Traversable[_] //The _ means it is existential, in this case
|
||||||
|
//I don't care what kind of Traversable it is.
|
||||||
|
def count(t:T) = t.size //I know it's a Traversable but I'll define later which one.
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntListCounter extends Counter {
|
||||||
|
type T = List[Int]
|
||||||
|
override def count(t:T) = t.sum //overriding implementation; Sum is
|
||||||
|
//only available with Numeric types
|
||||||
|
}
|
||||||
|
|
||||||
|
new IntListCounter().count(List(1,2,3,4)) should be (10)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
package harkonnen {
|
||||||
|
|
||||||
|
class AlphaBase extends KoanSuite with ShouldMatchers {
|
||||||
|
private val melange = 1
|
||||||
|
private[this] val oregano = 2
|
||||||
|
private[AlphaBase] val tarragon = 3
|
||||||
|
private[harkonnen] val rosemary = 4
|
||||||
|
|
||||||
|
val parsley = 5
|
||||||
|
// public val ginger = 6 //does not exist, in case you were wondering
|
||||||
|
// public[this] val lemongrass = 7 //does not exist, in case you were wondering
|
||||||
|
// public[AlphaBase] val carob = 8 //does not exist, in case you were wondering
|
||||||
|
// public[har] val celerySeed = 9 //does not exist, in case you were wondering
|
||||||
|
|
||||||
|
protected val sage = 10
|
||||||
|
protected[this] val paprika = 11
|
||||||
|
protected[AlphaBase] val saffron = 12
|
||||||
|
protected[harkonnen] val thyme = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BetaBase extends AlphaBase with KoanSuite with ShouldMatchers {
|
||||||
|
val param: AlphaBase = new AlphaBase
|
||||||
|
|
||||||
|
koan("With private keywords: Only private[packagename] members can be accessed via inheritance") {
|
||||||
|
//melange should be(1) //not accessible
|
||||||
|
//oregano should be (2) //not accessible
|
||||||
|
//tarragon should be (3) //not accessible
|
||||||
|
rosemary should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With private keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
//param.melange should be (1) //not accessible
|
||||||
|
//param.oregano should be (2) //not accessible
|
||||||
|
//param.tarragon should be (3) //not accessible
|
||||||
|
param.rosemary should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed through inheritance") {
|
||||||
|
parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed can be accessed via parameter") {
|
||||||
|
param.parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: All members can be accessed via inheritance") {
|
||||||
|
sage should be(10)
|
||||||
|
paprika should be(11) //not accessible
|
||||||
|
saffron should be(12) //not accessible
|
||||||
|
thyme should be(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
//param.sage should be (10) //not accessible
|
||||||
|
//param.paprika should be (11) //not accessible
|
||||||
|
//param.saffron should be (12) //not accessible
|
||||||
|
param.thyme should be(13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GammaBase extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
val param: AlphaBase = new AlphaBase
|
||||||
|
|
||||||
|
koan("With private keywords: No members can be accessed via inheritance") {
|
||||||
|
//melange should be(1) //not accessible
|
||||||
|
//oregano should be (2) //not accessible
|
||||||
|
//tarragon should be (3) //not accessible
|
||||||
|
//rosemary should be(4) //not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With private keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
//param.melange should be (1) //not accessible
|
||||||
|
//param.oregano should be (2) //not accessible
|
||||||
|
//param.tarragon should be (3) //not accessible
|
||||||
|
param.rosemary should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed through inheritance") {
|
||||||
|
//parsley should be(5) //not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed can be accessed via parameter") {
|
||||||
|
param.parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: All members can be accessed via inheritance") {
|
||||||
|
//sage should be (10)
|
||||||
|
//paprika should be (11) //not accessible
|
||||||
|
//saffron should be (12) //not accessible
|
||||||
|
//thyme should be (13)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
// param.sage should be (10) //not accessible
|
||||||
|
// param.paprika should be (11) //not accessible
|
||||||
|
// param.saffron should be (12) //not accessible
|
||||||
|
param.thyme should be(13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package atreides {
|
||||||
|
|
||||||
|
import org.functionalkoans.forscala.harkonnen.AlphaBase
|
||||||
|
|
||||||
|
class DeltaBase extends AlphaBase with KoanSuite with ShouldMatchers {
|
||||||
|
val param: AlphaBase = new AlphaBase
|
||||||
|
|
||||||
|
koan("With private keywords: Only private and private[packagename] members can be accessed via inheritance") {
|
||||||
|
//melange should be (1)
|
||||||
|
//oregano should be (2) //not accessible
|
||||||
|
//tarragon should be (3) //not accessible
|
||||||
|
//rosemary should be (4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With private keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
// param.melange should be (1) //not accessible
|
||||||
|
// param.oregano should be (2) //not accessible
|
||||||
|
// param.tarragon should be (3) //not accessible
|
||||||
|
// param.rosemary should be(4) //not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed through inheritance") {
|
||||||
|
parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed can be accessed via parameter") {
|
||||||
|
param.parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: All members can be accessed via inheritance") {
|
||||||
|
sage should be(10)
|
||||||
|
paprika should be(11) //not accessible
|
||||||
|
saffron should be(12) //not accessible
|
||||||
|
thyme should be(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
//param.sage should be (10) //not accessible
|
||||||
|
//param.paprika should be (11) //not accessible
|
||||||
|
//param.saffron should be (12) //not accessible
|
||||||
|
//param.thyme should be (13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpsilonBase extends KoanSuite with ShouldMatchers {
|
||||||
|
val param: AlphaBase = new AlphaBase
|
||||||
|
|
||||||
|
koan("With private keywords: Only private and private[packagename] members can be accessed via inheritance") {
|
||||||
|
// melange should be (1) //not accessible
|
||||||
|
// oregano should be (2) //not accessible
|
||||||
|
// tarragon should be (3) //not accessible
|
||||||
|
// rosemary should be (4) //not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With private keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
// param.melange should be (1) //not accessible
|
||||||
|
// param.oregano should be (2) //not accessible
|
||||||
|
// param.tarragon should be (3) //not accessible
|
||||||
|
// param.rosemary should be(4) //not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed through inheritance") {
|
||||||
|
// parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With public keywords: All members can be accessed can be accessed via parameter") {
|
||||||
|
param.parsley should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: All members can be accessed via inheritance") {
|
||||||
|
// sage should be (10)
|
||||||
|
// paprika should be (11) //not accessible
|
||||||
|
// saffron should be (12) //not accessible
|
||||||
|
// thyme should be (13)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("With protected keywords: Only private[packagename] members can be accessed via parameter") {
|
||||||
|
// param.sage should be (10) //not accessible
|
||||||
|
// param.paprika should be (11) //not accessible
|
||||||
|
// param.saffron should be (12) //not accessible
|
||||||
|
// param.thyme should be (13)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutAccessModifiers extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
}
|
||||||
131
src/test/scala/org/functionalkoans/forscala/AboutActors.scala
Normal file
131
src/test/scala/org/functionalkoans/forscala/AboutActors.scala
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import actors.Actor
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 4/27/11
|
||||||
|
* Time: 12:45 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
class AboutActors extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("Basic Actor that extends Actor, this will ben invoked in separate thread") {
|
||||||
|
import actors.Actor
|
||||||
|
class AbrahamLincoln extends Actor {
|
||||||
|
def act() {
|
||||||
|
println("Four score and seven years ago.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val abe = new AbrahamLincoln
|
||||||
|
abe.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Basic anonymous actor") {
|
||||||
|
import actors.Actor._
|
||||||
|
val jfk = actor {
|
||||||
|
println("Ask not what your country can do for you")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Messages can be sent to actors. The ! calls are inspired by Erlang
|
||||||
|
| React method returns no result""") {
|
||||||
|
import actors.Actor._
|
||||||
|
val guessNumber = actor {
|
||||||
|
loop {
|
||||||
|
react {
|
||||||
|
case i: Int if (i > 64) => println("Too high")
|
||||||
|
case i: Int if (i < 64) => println("Too low")
|
||||||
|
case i: Int if (i == 64) => {
|
||||||
|
println("Ding!")
|
||||||
|
exit('successful)
|
||||||
|
}
|
||||||
|
case _ => println("You gave me something wrong")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guessNumber ! 20
|
||||||
|
guessNumber ! 23
|
||||||
|
guessNumber ! 90
|
||||||
|
guessNumber ! 75
|
||||||
|
guessNumber ! 70
|
||||||
|
guessNumber ! 61
|
||||||
|
guessNumber ! "Boing"
|
||||||
|
guessNumber ! 65.34
|
||||||
|
guessNumber ! 64
|
||||||
|
|
||||||
|
Thread.sleep(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""case _ => is used as a catch all, if you do not adequately cover all possible scenarios, messages
|
||||||
|
will be held in an actors mail box.""") {
|
||||||
|
println("---------")
|
||||||
|
import actors.Actor._
|
||||||
|
val guessNumber = actor {
|
||||||
|
loop {
|
||||||
|
react {
|
||||||
|
case i: Int if (i > 64) => println("Too high")
|
||||||
|
case i: Int if (i < 64) => println("Too low")
|
||||||
|
case i: Int if (i == 64) => {
|
||||||
|
println("Ding!")
|
||||||
|
println(mailboxSize + " messages have not been matched")
|
||||||
|
exit('successful)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guessNumber ! 20
|
||||||
|
guessNumber ! 23
|
||||||
|
guessNumber ! "Boing"
|
||||||
|
guessNumber ! 90
|
||||||
|
guessNumber ! 75
|
||||||
|
guessNumber ! 70
|
||||||
|
guessNumber ! 61
|
||||||
|
guessNumber ! 64
|
||||||
|
guessNumber ! "Boom"
|
||||||
|
guessNumber ! 65.34
|
||||||
|
Thread.sleep(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Up until now we have been Actors the way it wasn't intended. Actors are intended
|
||||||
|
to communicate back and forth by message passing using the ! operator.
|
||||||
|
self is to reference the current Actor.""") {
|
||||||
|
|
||||||
|
println("---------")
|
||||||
|
import actors.Actor._
|
||||||
|
val guessNumber = actor {
|
||||||
|
loop {
|
||||||
|
react {
|
||||||
|
case (i: Int, caller: Actor) if (i > 64) => caller ! "Too High"
|
||||||
|
case (i: Int, caller: Actor) if (i < 64) => caller ! "Too Low"
|
||||||
|
case (i: Int, caller: Actor) if (i == 64) => {
|
||||||
|
caller ! "Ding"
|
||||||
|
exit('successful)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = List(20, 23, "Boing", 90, 75, 70, 61, 64, "Boom", 65.34)
|
||||||
|
items.foreach {
|
||||||
|
x =>
|
||||||
|
guessNumber ! (x, self)
|
||||||
|
self.receiveWithin(100) {
|
||||||
|
case "Too High" => println("Too High")
|
||||||
|
case "Too Low" => println("Too Low")
|
||||||
|
case "Ding" => println("Just Right")
|
||||||
|
case _ => println("Timed Out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(1000) //Wait until all of it is completely done.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.BlankValues.__
|
||||||
|
import support.Blankout.blank
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
// meditate on AboutAsserts to see how the Scala Koans work
|
||||||
|
class AboutAsserts extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("asserts can take a boolean argument") {
|
||||||
|
assert(true) // should be true
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("asserts can include a message") {
|
||||||
|
assert(true, "This should be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("true and false values can be compared with should matchers") {
|
||||||
|
true should be(true) // should be true
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("booleans in asserts can test equality") {
|
||||||
|
val v1 = 4
|
||||||
|
val v2 = 4
|
||||||
|
assert(v1 === v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("sometimes we expect you to fill in the values") {
|
||||||
|
assert(2 == 1 + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutCaseClasses extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
// case classes are very convenient, they give you a lot for free. The following Koans will
|
||||||
|
// help you understand some of the conveniences. Case classes are also an integral part of
|
||||||
|
// pattern matching which are the subject of separate koan
|
||||||
|
|
||||||
|
koan("Case classes have an automatic equals method that works") {
|
||||||
|
case class Person(first: String, last: String)
|
||||||
|
|
||||||
|
val p1 = new Person("Fred", "Jones")
|
||||||
|
val p2 = new Person("Shaggy", "Rogers")
|
||||||
|
val p3 = new Person("Fred", "Jones")
|
||||||
|
|
||||||
|
(p1 == p2) should be(false)
|
||||||
|
(p1 == p3) should be(true)
|
||||||
|
|
||||||
|
(p1 eq p2) should be(false)
|
||||||
|
(p1 eq p3) should be(false) // not identical, merely equal
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes have an automatic hashcode method that works") {
|
||||||
|
case class Person(first: String, last: String)
|
||||||
|
|
||||||
|
val p1 = new Person("Fred", "Jones")
|
||||||
|
val p2 = new Person("Shaggy", "Rogers")
|
||||||
|
val p3 = new Person("Fred", "Jones")
|
||||||
|
|
||||||
|
(p1.hashCode == p2.hashCode) should be(false)
|
||||||
|
(p1.hashCode == p3.hashCode) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes have a convenient way they can be created") {
|
||||||
|
case class Dog(name: String, breed: String)
|
||||||
|
|
||||||
|
val d1 = Dog("Scooby", "Doberman")
|
||||||
|
val d2 = Dog("Rex", "Custom")
|
||||||
|
val d3 = new Dog("Scooby", "Doberman") // the old way of creating using new
|
||||||
|
|
||||||
|
(d1 == d3) should be(true)
|
||||||
|
(d1 == d2) should be(false)
|
||||||
|
(d2 == d3) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes have a convenient toString method defined") {
|
||||||
|
case class Dog(name: String, breed: String)
|
||||||
|
val d1 = Dog("Scooby", "Doberman")
|
||||||
|
d1.toString should be("Dog(Scooby,Doberman)")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes have automatic properties") {
|
||||||
|
case class Dog(name: String, breed: String)
|
||||||
|
|
||||||
|
val d1 = Dog("Scooby", "Doberman")
|
||||||
|
d1.name should be("Scooby")
|
||||||
|
d1.breed should be("Doberman")
|
||||||
|
|
||||||
|
// what happens if you uncomment the line below? Why?
|
||||||
|
//d1.name = "Scooby Doo"
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes can have mutable properties") {
|
||||||
|
case class Dog(var name: String, breed: String) // you can rename a dog, but change its breed? nah!
|
||||||
|
val d1 = Dog("Scooby", "Doberman")
|
||||||
|
|
||||||
|
d1.name should be("Scooby")
|
||||||
|
d1.breed should be("Doberman")
|
||||||
|
|
||||||
|
d1.name = "Scooby Doo" // but is it a good idea?
|
||||||
|
|
||||||
|
d1.name should be("Scooby Doo")
|
||||||
|
d1.breed should be("Doberman")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Safer alternatives exist for altering case classes") {
|
||||||
|
case class Dog(name: String, breed: String) // Doberman
|
||||||
|
|
||||||
|
val d1 = Dog("Scooby", "Doberman")
|
||||||
|
|
||||||
|
val d2 = d1.copy(name = "Scooby Doo") // copy the case class but change the name in the copy
|
||||||
|
|
||||||
|
d1.name should be("Scooby") // original left alone
|
||||||
|
d1.breed should be("Doberman")
|
||||||
|
|
||||||
|
d2.name should be("Scooby Doo")
|
||||||
|
d2.breed should be("Doberman") // copied from the original
|
||||||
|
}
|
||||||
|
|
||||||
|
// case class has to be defined outside of the test for this one
|
||||||
|
case class Person(first: String, last: String, age: Int = 0, ssn: String = "")
|
||||||
|
|
||||||
|
koan("Case classes have default and named parameters") {
|
||||||
|
|
||||||
|
val p1 = Person("Fred", "Jones", 23, "111-22-3333")
|
||||||
|
val p2 = Person("Samantha", "Jones") // note missing age and ssn
|
||||||
|
val p3 = Person(last = "Jones", first = "Fred", ssn = "111-22-3333") // note the order can change, and missing age
|
||||||
|
val p4 = p3.copy(age = 23)
|
||||||
|
|
||||||
|
p1.first should be("Fred")
|
||||||
|
p1.last should be("Jones")
|
||||||
|
p1.age should be(23)
|
||||||
|
p1.ssn should be("111-22-3333")
|
||||||
|
|
||||||
|
p2.first should be("Samantha")
|
||||||
|
p2.last should be("Jones")
|
||||||
|
p2.age should be(0)
|
||||||
|
p2.ssn should be("")
|
||||||
|
|
||||||
|
p3.first should be("Fred")
|
||||||
|
p3.last should be("Jones")
|
||||||
|
p3.age should be(0)
|
||||||
|
p3.ssn should be("111-22-3333")
|
||||||
|
|
||||||
|
(p1 == p4) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Case classes can be disassembled to their constituent parts as a tuple") {
|
||||||
|
val p1 = Person("Fred", "Jones", 23, "111-22-3333")
|
||||||
|
|
||||||
|
val parts = Person.unapply(p1).get // this seems weird, but it's critical to other features of Scala
|
||||||
|
|
||||||
|
parts._1 should be("Fred")
|
||||||
|
parts._2 should be("Jones")
|
||||||
|
parts._3 should be(23)
|
||||||
|
parts._4 should be("111-22-3333")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.BlankValues.__
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutClasses extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
|
||||||
|
// you can define class with var or val parameters
|
||||||
|
class ClassWithVarParameter(var description: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("val parameters in class definition define getter") {
|
||||||
|
val aClass = new ClassWithValParameter("name goes here")
|
||||||
|
aClass.name should be("name goes here");
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassWithValParameter(val name: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("var parameters in class definition define getter and setter") {
|
||||||
|
val aClass = new ClassWithVarParameter("description goes here")
|
||||||
|
aClass.description should be("description goes here");
|
||||||
|
|
||||||
|
aClass.description = "new description"
|
||||||
|
aClass.description should be("new description")
|
||||||
|
}
|
||||||
|
|
||||||
|
// you can define class with private fields
|
||||||
|
class ClassWithPrivateFields(name: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("fields defined internally are private to class") {
|
||||||
|
val aClass = new ClassWithPrivateFields("name")
|
||||||
|
|
||||||
|
// NOTE: aClass.name is not accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutConstructors extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class AboutConstructorWithValParameter(val name: String) {
|
||||||
|
// invoke auxilary constructor
|
||||||
|
def this() {
|
||||||
|
// what happens if you comment out the following line?
|
||||||
|
this ("defaultname")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutClassWithNoClassParameter {
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutConstructorWithVarParameter(var name: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutConstructorWithPrivateClassParameter(name: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("val in class definition defines read only property") {
|
||||||
|
val aboutMe = new AboutConstructorWithValParameter("MyName")
|
||||||
|
aboutMe.name should be("MyName")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("var in class definition defines read/write parameters") {
|
||||||
|
val aboutMe = new AboutConstructorWithVarParameter("MyName")
|
||||||
|
aboutMe.name = "YourName"
|
||||||
|
aboutMe.name should be("YourName")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("private member data is not accessible") {
|
||||||
|
val aboutMe = new AboutConstructorWithPrivateClassParameter("MyName")
|
||||||
|
|
||||||
|
// what happens if you uncomment this line? why?
|
||||||
|
// aboutMe.name = "Me"
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Primary constructor specified with a parameter requires that parameter to be passed in") {
|
||||||
|
// val aboutMe = new AboutConstructorWithValParameter()
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Class with no class parameters is called with no arguments") {
|
||||||
|
// add parameter to make this fail
|
||||||
|
val aboutMe = new AboutClassWithNoClassParameter
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
|
||||||
|
class AboutEmptyValues extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
test("None equals None") {
|
||||||
|
assert(None === None)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("None should be identical to None") {
|
||||||
|
val a = None
|
||||||
|
assert(a eq None) // note that eq denotes identity, and == denotes equality in Scala
|
||||||
|
}
|
||||||
|
|
||||||
|
test("None can be converted to a String") {
|
||||||
|
assert(None.toString === "None")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("An empty list can be represented by another nothing value: Nil") {
|
||||||
|
assert(List() === Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("None can be converted to an empty list") {
|
||||||
|
val a = None
|
||||||
|
assert(a.toList === Nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("None is considered empty") {
|
||||||
|
assert(None.isEmpty === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*test ("None can be cast Any, AnyRef or AnyVal") {
|
||||||
|
assert(None.asInstanceOf[Any] === __)
|
||||||
|
assert(None.asInstanceOf[AnyRef] === __)
|
||||||
|
assert(None.asInstanceOf[AnyVal] === __)
|
||||||
|
} */
|
||||||
|
|
||||||
|
test("None cannot be cast to all types of objects") {
|
||||||
|
intercept[ClassCastException] {
|
||||||
|
// put the exception you expect to see in place of the blank
|
||||||
|
assert(None.asInstanceOf[String] === classOf[ClassCastException])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("None can be used with Option instead of null references") {
|
||||||
|
val optional: Option[String] = None
|
||||||
|
assert(optional.isEmpty === true)
|
||||||
|
assert(optional === None)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Some is the opposite of None for Option types") {
|
||||||
|
val optional: Option[String] = Some("Some Value")
|
||||||
|
assert((optional == None) === false, "Some(value) should not equal None")
|
||||||
|
assert(optional.isEmpty === false, "Some(value) should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Option.getOrElse can be used to provide a default in the case of None") {
|
||||||
|
val optional: Option[String] = Some("Some Value")
|
||||||
|
val optional2: Option[String] = None
|
||||||
|
assert(optional.getOrElse("No Value") === "Some Value", "Should return the value in the option")
|
||||||
|
assert(optional2.getOrElse("No Value") === "No Value", "Should return the specified default value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
|
||||||
|
class AboutEnumerations extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
// To create an enumeration, create an object that extends the abstract class Enumeration,
|
||||||
|
// and set a val variable to the method Value. This is a trick to give values to each val."
|
||||||
|
koan("Value assigns a numerical value to fields") {
|
||||||
|
|
||||||
|
object Planets extends Enumeration {
|
||||||
|
val Mercury = Value
|
||||||
|
val Venus = Value
|
||||||
|
val Earth = Value
|
||||||
|
val Mars = Value
|
||||||
|
val Jupiter = Value
|
||||||
|
val Saturn = Value
|
||||||
|
val Uranus = Value
|
||||||
|
val Neptune = Value
|
||||||
|
val Pluto = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
Planets.Mercury.id should be(0)
|
||||||
|
Planets.Venus.id should be(1)
|
||||||
|
|
||||||
|
Planets.Mercury.toString should be("Mercury") //How does it get the name? by Reflection.
|
||||||
|
Planets.Venus.toString should be("Venus")
|
||||||
|
|
||||||
|
(Planets.Earth == Planets.Earth) should be(true)
|
||||||
|
(Planets.Neptune == Planets.Jupiter) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can create an enumeration with your own index and your own Strings, in this koan,
|
||||||
|
// we will start with an index of one and use Greek names instead of Roman
|
||||||
|
koan("Enumerations can set their own index and name") {
|
||||||
|
object GreekPlanets extends Enumeration {
|
||||||
|
|
||||||
|
val Mercury = Value(1, "Hermes")
|
||||||
|
val Venus = Value(2, "Aphrodite")
|
||||||
|
//FYI: Tellus is Roman for (Mother) Earth
|
||||||
|
val Earth = Value(3, "Gaia")
|
||||||
|
val Mars = Value(4, "Ares")
|
||||||
|
val Jupiter = Value(5, "Zeus")
|
||||||
|
val Saturn = Value(6, "Cronus")
|
||||||
|
val Uranus = Value(7, "Ouranus")
|
||||||
|
val Neptune = Value(8, "Poseidon")
|
||||||
|
val Pluto = Value(9, "Hades")
|
||||||
|
}
|
||||||
|
|
||||||
|
GreekPlanets.Mercury.id should be(1)
|
||||||
|
GreekPlanets.Venus.id should be(2)
|
||||||
|
|
||||||
|
GreekPlanets.Mercury.toString should be("Hermes")
|
||||||
|
GreekPlanets.Venus.toString should be("Aphrodite")
|
||||||
|
|
||||||
|
(GreekPlanets.Earth == GreekPlanets.Earth) should be(true)
|
||||||
|
(GreekPlanets.Neptune == GreekPlanets.Jupiter) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enumerations can be declared in one line if you are merely setting variables to Value
|
||||||
|
koan("Enumeration declarations can be done on one line") {
|
||||||
|
object Planets extends Enumeration {
|
||||||
|
val Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto = Value
|
||||||
|
}
|
||||||
|
|
||||||
|
Planets.Mercury.id should be(0)
|
||||||
|
Planets.Venus.id should be(1)
|
||||||
|
|
||||||
|
Planets.Mercury.toString should be("Mercury") //How does it get the name? by Reflection.
|
||||||
|
Planets.Venus.toString should be("Venus")
|
||||||
|
|
||||||
|
(Planets.Earth == Planets.Earth) should be(true)
|
||||||
|
(Planets.Neptune == Planets.Jupiter) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("Enumerations can be declared with a string value only") {
|
||||||
|
object GreekPlanets extends Enumeration {
|
||||||
|
|
||||||
|
val Mercury = Value("Hermes")
|
||||||
|
val Venus = Value("Aphrodite")
|
||||||
|
val Earth = Value("Gaia")
|
||||||
|
val Mars = Value("Ares")
|
||||||
|
val Jupiter = Value("Zeus")
|
||||||
|
val Saturn = Value("Cronus")
|
||||||
|
val Uranus = Value("Ouranus")
|
||||||
|
val Neptune = Value("Poseidon")
|
||||||
|
val Pluto = Value("Hades")
|
||||||
|
}
|
||||||
|
|
||||||
|
GreekPlanets.Mercury.id should be(0)
|
||||||
|
GreekPlanets.Venus.id should be(1)
|
||||||
|
|
||||||
|
GreekPlanets.Mercury.toString should be("Hermes")
|
||||||
|
GreekPlanets.Venus.toString should be("Aphrodite")
|
||||||
|
|
||||||
|
(GreekPlanets.Earth == GreekPlanets.Earth) should be(true)
|
||||||
|
(GreekPlanets.Neptune == GreekPlanets.Jupiter) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can extend the Enumeration by extending the Val class.") {
|
||||||
|
|
||||||
|
object Planets extends Enumeration {
|
||||||
|
|
||||||
|
val G = 6.67300E-11;
|
||||||
|
|
||||||
|
class PlanetValue(val i: Int, val name: String, val mass: Double, val radius: Double)
|
||||||
|
extends Val(i: Int, name: String) {
|
||||||
|
|
||||||
|
def surfaceGravity = G * mass / (radius * radius)
|
||||||
|
|
||||||
|
def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity
|
||||||
|
}
|
||||||
|
|
||||||
|
val Mercury = new PlanetValue(0, "Mercury", 3.303e+23, 2.4397e6)
|
||||||
|
val Venus = new PlanetValue(1, "Venus", 4.869e+24, 6.0518e6)
|
||||||
|
val Earth = new PlanetValue(2, "Earth", 5.976e+24, 6.37814e6)
|
||||||
|
val Mars = new PlanetValue(3, "Mars", 6.421e+23, 3.3972e6)
|
||||||
|
val Jupiter = new PlanetValue(4, "Jupiter", 1.9e+27, 7.1492e7)
|
||||||
|
val Saturn = new PlanetValue(5, "Saturn", 5.688e+26, 6.0268e7)
|
||||||
|
val Uranus = new PlanetValue(6, "Uranus", 8.686e+25, 2.5559e7)
|
||||||
|
val Neptune = new PlanetValue(7, "Neptune", 1.024e+26, 2.4746e7)
|
||||||
|
val Pluto = new PlanetValue(8, "Pluto", 1.27e+22, 1.137e6)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Planets.Earth.mass should be(5.976e+24)
|
||||||
|
Planets.Earth.radius should be(6.37814e6)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutForExpressions extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("For loops can be simple") {
|
||||||
|
val someNumbers = Range(0, 10)
|
||||||
|
var sum = 0
|
||||||
|
for (i <- someNumbers)
|
||||||
|
sum += i
|
||||||
|
|
||||||
|
sum should equal(45)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("For loops can contain additional logic") {
|
||||||
|
val someNumbers = Range(0, 10)
|
||||||
|
var sum = 0
|
||||||
|
// sum only the even numbers
|
||||||
|
for (i <- someNumbers)
|
||||||
|
if (i % 2 == 0) sum += i
|
||||||
|
|
||||||
|
sum should equal(20)
|
||||||
|
}
|
||||||
|
koan("For loops can produce a list which can be summed easily") {
|
||||||
|
val someNumbers = Range(0, 10)
|
||||||
|
|
||||||
|
val theList =
|
||||||
|
for {
|
||||||
|
i <- someNumbers
|
||||||
|
if ((i % 2) == 0)
|
||||||
|
}
|
||||||
|
yield i
|
||||||
|
|
||||||
|
theList.reduceLeft(_ + _) should be(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("For expressions can nest, with later generators varying more rapidly than earlier ones") {
|
||||||
|
val xValues = Range(1, 5)
|
||||||
|
val yValues = Range(1, 3)
|
||||||
|
val coordinates = for {
|
||||||
|
x <- xValues
|
||||||
|
y <- yValues
|
||||||
|
}
|
||||||
|
yield (x, y)
|
||||||
|
coordinates(4) should be(3, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutFormatting extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""Character Literals can either be an a single character,
|
||||||
|
| an escape sequence, a Unicode octal up to 255 or a hexadecimal""") {
|
||||||
|
val a = 'a'
|
||||||
|
val b = 'B'
|
||||||
|
val c = '\u0061' //unicode for a
|
||||||
|
val d = '\141' //octal for a
|
||||||
|
val e = '\"'
|
||||||
|
val f = '\\'
|
||||||
|
|
||||||
|
//format(a) is a string format, meaning the "%c".format(x)
|
||||||
|
//will return the string representation of the char.
|
||||||
|
|
||||||
|
"%c".format(a) should be("a")
|
||||||
|
"%c".format(b) should be("B")
|
||||||
|
"%c".format(c) should be("a")
|
||||||
|
"%c".format(d) should be("a")
|
||||||
|
"%c".format(e) should be("\"")
|
||||||
|
"%c".format(f) should be("\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import collection.immutable.List
|
||||||
|
|
||||||
|
class AboutHigherOrderFunctions extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("A variable referencing an anonymous function") {
|
||||||
|
val lambda = {
|
||||||
|
x: Int => x + 1
|
||||||
|
}
|
||||||
|
def result = List(1, 2, 3) map lambda //FYI: map runs a function on each element of a collection
|
||||||
|
result should be(List(2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Another way for a variable to reference an anonymous function") {
|
||||||
|
val lambda = (x: Int) => x + 1
|
||||||
|
def result = List(1, 2, 3) map lambda
|
||||||
|
result should be(List(2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Declaring a variable to reference an explicitly created function") {
|
||||||
|
val lambda = new Function1[Int, Int] {
|
||||||
|
def apply(v1: Int) = v1 + 1
|
||||||
|
}
|
||||||
|
def result = List(1, 2, 3) map lambda
|
||||||
|
result should be(List(2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Another way for a variable to reference an explicitly created function. In this case (Int) => Int
|
||||||
|
| is the same type as Function1[Int, Int]""") {
|
||||||
|
val lambda = new ((Int) => Int) {
|
||||||
|
def apply(v1: Int) = v1 + 1
|
||||||
|
}
|
||||||
|
def result = List(1, 2, 3) map lambda
|
||||||
|
result should be(List(2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A closure is any function that works with it's surroundings") {
|
||||||
|
var incrementor = 1
|
||||||
|
|
||||||
|
def closure = {
|
||||||
|
x: Int => x + incrementor
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = List(1, 2, 3) map closure
|
||||||
|
result should be(List(2, 3, 4))
|
||||||
|
|
||||||
|
incrementor = 2
|
||||||
|
|
||||||
|
val result1 = List(1, 2, 3) map closure
|
||||||
|
result1 should be(List(3, 4, 5))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A function returning another function") {
|
||||||
|
def addWithoutSyntaxSugar(x: Int) = {
|
||||||
|
new Function1[Int, Int]() {
|
||||||
|
def apply(y: Int): Int = x + y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addWithoutSyntaxSugar(1)(2) should be(3)
|
||||||
|
|
||||||
|
def add(x: Int) = (y: Int) => x + y
|
||||||
|
add(2)(3) should be(5)
|
||||||
|
|
||||||
|
def fiveAdder = add(5)
|
||||||
|
fiveAdder(5) should be(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("function taking another function as parameter. Helps in compositioning functions") {
|
||||||
|
def makeUpper(xs: List[String]) = xs map {
|
||||||
|
_.toUpperCase
|
||||||
|
}
|
||||||
|
def makeWhatEverYouLike(xs: List[String], sideEffect: String => String) = {
|
||||||
|
xs map sideEffect
|
||||||
|
}
|
||||||
|
makeUpper(List("abc", "xyz", "123")) should be(List("ABC", "XYZ", "123"))
|
||||||
|
|
||||||
|
makeWhatEverYouLike(List("ABC", "XYZ", "123"), {
|
||||||
|
x => x.toLowerCase
|
||||||
|
}) should be(List("abc", "xyz", "123"))
|
||||||
|
//using it inline
|
||||||
|
List("Scala", "Erlang", "Clojure") map {
|
||||||
|
_.length
|
||||||
|
} should be(List(5, 6, 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Currying is a technique to transform function with multiple parameters to function with one parameter") {
|
||||||
|
def multiply(x: Int, y: Int) = x * y
|
||||||
|
val multiplyCurried = (multiply _).curried
|
||||||
|
multiply(4, 5) should be(20)
|
||||||
|
multiplyCurried(3)(2) should be(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Currying allows you to create specialized version of generalized function") {
|
||||||
|
def customFilter(f: Int => Boolean)(xs: List[Int]) = {
|
||||||
|
xs filter f
|
||||||
|
}
|
||||||
|
def onlyEven(x: Int) = x % 2 == 0
|
||||||
|
val xs = List(12, 11, 5, 20, 3, 13, 2)
|
||||||
|
customFilter(onlyEven)(xs) should be(List(12, 20, 2))
|
||||||
|
|
||||||
|
val onlyEvenFilter = customFilter(onlyEven) _
|
||||||
|
onlyEvenFilter(xs) should be(List(12, 20, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Using a method inside a class to create a function") {
|
||||||
|
class PokerTable(name: String, capacity: Int, players: List[String] = Nil) {
|
||||||
|
def isAvailable(amount: Int) = (capacity - amount < 0)
|
||||||
|
|
||||||
|
def addPlayer(player: String) = {
|
||||||
|
require(players.size < capacity, "Table Full")
|
||||||
|
new PokerTable(name, capacity, players :+ player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val theTexas = new PokerTable("The Texas", 9)
|
||||||
|
val oldWest = new PokerTable("Old West", 3, Nil)
|
||||||
|
val whirlwind = new PokerTable("Whirlwind", 3, Nil)
|
||||||
|
val enchantress = new PokerTable("Enchantress", 15, Nil)
|
||||||
|
|
||||||
|
val x = theTexas.isAvailable _
|
||||||
|
List(12, 2, 3, 4, 5, 6, 11, 44).filter(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 3/10/11
|
||||||
|
* Time: 5:38 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
class AboutImplicits extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""Implicits wrap around existing classes to provide extra functionality
|
||||||
|
| This is similar to \'monkey patching\' in Ruby, and Meta-Programming in Groovy.
|
||||||
|
| Creating a method isOdd for Int, which doesn't exist""") {
|
||||||
|
|
||||||
|
class KoanIntWrapper(val original: Int) {
|
||||||
|
def isOdd() = original % 2 != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def thisMethodNameIsIrrelevant(value: Int) = new KoanIntWrapper(value)
|
||||||
|
|
||||||
|
19.isOdd() should be(true)
|
||||||
|
20.isOdd() should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Implicits rules can be imported into your scope with an import""") {
|
||||||
|
object MyPredef {
|
||||||
|
|
||||||
|
class KoanIntWrapper(val original: Int) {
|
||||||
|
def isOdd() = original % 2 != 0
|
||||||
|
|
||||||
|
def isEven() = !isOdd()
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit def thisMethodNameIsIrrelevant(value: Int) = new KoanIntWrapper(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
import MyPredef._
|
||||||
|
//imported implicits come into effect within this scope
|
||||||
|
19.isOdd() should be(true)
|
||||||
|
20.isOdd() should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Implicits can be used to automatically convert one type to another""") {
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
implicit def Int2BigIntegerConvert(value: Int): BigInteger = new BigInteger(value.toString)
|
||||||
|
|
||||||
|
def add(a: BigInteger, b: BigInteger) = a.add(b)
|
||||||
|
|
||||||
|
(add(3, 6)) should be(new BigInteger("9"))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Implicits can be used declare a value to be provided as a default as
|
||||||
|
| long as an implicit value is set with in the scope. These are
|
||||||
|
| called implicit function parameters""") {
|
||||||
|
|
||||||
|
def howMuchCanIMake_?(hours: Int)(implicit dollarsPerHour: BigDecimal) = dollarsPerHour * hours
|
||||||
|
|
||||||
|
implicit var hourlyRate = BigDecimal(34.00)
|
||||||
|
howMuchCanIMake_?(30) should be(1020.00)
|
||||||
|
|
||||||
|
hourlyRate = BigDecimal(95.00)
|
||||||
|
howMuchCanIMake_?(95) should be(9025.00)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Implicit Function Parameters can contain a list of implicits""") {
|
||||||
|
|
||||||
|
def howMuchCanIMake_?(hours: Int)(implicit amount: BigDecimal, currencyName: String) =
|
||||||
|
(amount * hours).toString() + " " + currencyName
|
||||||
|
|
||||||
|
implicit var hourlyRate = BigDecimal(34.00)
|
||||||
|
implicit val currencyName = "Dollars"
|
||||||
|
|
||||||
|
howMuchCanIMake_?(30) should be("1020.0 Dollars")
|
||||||
|
|
||||||
|
hourlyRate = BigDecimal(95.00)
|
||||||
|
howMuchCanIMake_?(95) should be("9025.0 Dollars")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Default arguments though are preferred to Implicit Function Parameters""") {
|
||||||
|
|
||||||
|
def howMuchCanIMake_?(hours: Int, amount: BigDecimal = 34, currencyName: String = "Dollars") =
|
||||||
|
(amount * hours).toString() + " " + currencyName
|
||||||
|
|
||||||
|
howMuchCanIMake_?(30) should be("1020 Dollars")
|
||||||
|
|
||||||
|
howMuchCanIMake_?(95, 95) should be("9025 Dollars")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import org.functionalkoans.forscala.support.KoanSuite
|
||||||
|
|
||||||
|
class AboutImportsAndPackages extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("An import can be placed in a method, hint this koan is a method") {
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
val arrayBuffer = ArrayBuffer.range(2, 10)
|
||||||
|
arrayBuffer(0) should be(2)
|
||||||
|
arrayBuffer(1) should be(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Artist(val firstName: String, val lastName: String)
|
||||||
|
|
||||||
|
package subpackage {
|
||||||
|
|
||||||
|
class AboutImportsAndPackagesInSubpackages extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("A package can be included in a file with an established established package, " +
|
||||||
|
"and can encapsulate it's contents with a {} block") {
|
||||||
|
val luther = new Artist("Luther", "Vandross")
|
||||||
|
luther.lastName should be("Vandross")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package album {
|
||||||
|
|
||||||
|
class Album(val name: String, val year: Short, val artist: Artist)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package media {
|
||||||
|
|
||||||
|
|
||||||
|
class AboutReferencingAbsolutePackages extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
import org.functionalkoans.forscala.album.Album
|
||||||
|
|
||||||
|
// <<< Note the import style
|
||||||
|
koan("A import can be done based from absolute package heirarchy") {
|
||||||
|
val stLouisBlues = new Album("St. Louis Blues", 1940, new Artist("Louie", "Armstrong"))
|
||||||
|
stLouisBlues.getClass.getCanonicalName should be("org.functionalkoans.forscala.album.Album")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutReferencingAbsoluteRootPackages extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
import _root_.org.functionalkoans.forscala.album.Album
|
||||||
|
|
||||||
|
// <<< Note the import style
|
||||||
|
koan("A import can be done based from absolute root package heirarchy using _root_") {
|
||||||
|
val stLouisBlues = new Album("St. Louis Blues", 1940, new Artist("Louie", "Armstrong"))
|
||||||
|
stLouisBlues.getClass.getCanonicalName should be("org.functionalkoans.forscala.album.Album")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutReferencingRelativePackages extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
import album.Album
|
||||||
|
|
||||||
|
// <<< Note the import style
|
||||||
|
koan("A import can be done based from relative packaging") {
|
||||||
|
val stLouisBlues = new Album("St. Louis Blues", 1940, new Artist("Louie", "Armstrong"))
|
||||||
|
stLouisBlues.getClass.getCanonicalName should be("org.functionalkoans.forscala.album.Album")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
package music_additions {
|
||||||
|
|
||||||
|
class Genre(val name: String)
|
||||||
|
|
||||||
|
class Producer(val firstName: String, lastName: String)
|
||||||
|
|
||||||
|
class Distributor(val name: String)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AboutImportingTechniques extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("To import all classes of a package, use _ as a wildcard") {
|
||||||
|
import music_additions._
|
||||||
|
val genre = new Genre("Jazz")
|
||||||
|
val producer = new Producer("Joe", "Oliver")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
genre.name should be("Jazz")
|
||||||
|
producer.firstName should be("Joe")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("To import all classes of a package, use can also use {_} as a wildcard") {
|
||||||
|
import music_additions.{_}
|
||||||
|
val genre = new Genre("Jazz")
|
||||||
|
val producer = new Producer("Joe", "Oliver")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
genre.name should be("Jazz")
|
||||||
|
producer.firstName should be("Joe")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("To import a select group of classes of a package, use {className1, className}") {
|
||||||
|
import music_additions.{Genre, Distributor}
|
||||||
|
val genre = new Genre("Jazz")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
genre.name should be("Jazz")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can rename a class by using => and create an alias") {
|
||||||
|
import music_additions.{Genre => MusicType, Distributor}
|
||||||
|
val musicType = new MusicType("Jazz")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
musicType.name should be("Jazz")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can rename a class by using =>, and also import all other classes in a package keeping their name") {
|
||||||
|
import music_additions.{Genre => MusicType, _}
|
||||||
|
val musicType = new MusicType("Jazz")
|
||||||
|
val producer = new Producer("Joe", "Oliver")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
musicType.name should be("Jazz")
|
||||||
|
producer.firstName should be("Joe")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can also refuse classes from being imported using => _") {
|
||||||
|
import music_additions.{Producer => _, _}
|
||||||
|
val musicType = new Genre("Jazz")
|
||||||
|
val distributor = new Distributor("RYKO Classic Music")
|
||||||
|
|
||||||
|
musicType.name should be("Jazz")
|
||||||
|
distributor.name should be("RYKO Classic Music")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can just import the package themselves,so you can give it a verbose identity") {
|
||||||
|
import scala.collection.mutable
|
||||||
|
val arrayBuffer = mutable.ArrayBuffer.range(2, 10) //sounds better: A Mutable ArrayBuffer
|
||||||
|
arrayBuffer(0) should be(2)
|
||||||
|
arrayBuffer(1) should be(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can just import the package themselves, and give it an alias!") {
|
||||||
|
import scala.collection.{mutable => changeable}
|
||||||
|
val arrayBuffer = changeable.ArrayBuffer.range(2, 10)
|
||||||
|
arrayBuffer(0) should be(2)
|
||||||
|
arrayBuffer(1) should be(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 4/25/11
|
||||||
|
* Time: 9:32 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
class AboutInfixPrefixAndPostfixOperators extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""Simple: Infix Operators are available if an object
|
||||||
|
| has a method that takes one parameter.""") {
|
||||||
|
|
||||||
|
val g: Int = 3
|
||||||
|
(g + 4) should be(7) // + is an infix operator
|
||||||
|
(g.+(4)) should be(7) // same result but not using the infix operator
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Infix Operators do NOT work if an object
|
||||||
|
| has a method that takes two parameters.""") {
|
||||||
|
val g: String = "Check out the big brains on Brad!"
|
||||||
|
g indexOf 'o' should be(6) //indexOf(Char) can be used as an infix operator
|
||||||
|
//g indexOf 'o', 4 should be (6) //indexOf(Char, Int) cannot be used an infix operator
|
||||||
|
g.indexOf('o', 7) should be(25) //indexOf(Char, Int) must use standard java/scala calls
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Postfix operators work if an object
|
||||||
|
| has a method that takes no parameters.""") {
|
||||||
|
val g: Int = 31
|
||||||
|
(g toHexString) should be("1f") //toHexString takes no params therefore can be called
|
||||||
|
//as a postfix operator. Hint: The answer is 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Prefix operators work if an object
|
||||||
|
| has a method name that starts with unary_ .""") {
|
||||||
|
val g: Int = 31
|
||||||
|
(-31) should be(-31)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Here we create our own prefix operator for our own class.
|
||||||
|
| The only identifiers that can be used as prefix operators
|
||||||
|
| are +, -, !, and ~""") {
|
||||||
|
|
||||||
|
class Stereo {
|
||||||
|
def unary_+ = "on"
|
||||||
|
|
||||||
|
def unary_- = "off"
|
||||||
|
}
|
||||||
|
|
||||||
|
val stereo = new Stereo
|
||||||
|
(+stereo) should be("on")
|
||||||
|
(-stereo) should be("off")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 4/25/11
|
||||||
|
* Time: 10:17 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
class AboutInfixTypes extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""We can make a type infix, meaning that the type can be displayed in complement
|
||||||
|
between two types in order to make a readable delaration""") {
|
||||||
|
case class Person(name: String)
|
||||||
|
class Loves[A, B](val a: A, val b: B)
|
||||||
|
|
||||||
|
def announceCouple(couple: Person Loves Person) = {
|
||||||
|
//Notice our type: Person loves Person!
|
||||||
|
couple.a.name + " is in love with " + couple.b.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val romeo = new Person("Romeo")
|
||||||
|
val juliet = new Person("Juliet")
|
||||||
|
|
||||||
|
announceCouple(new Loves(romeo, juliet)) should be("Romeo is in love with Juliet")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Of course we can make this a bit more elegant by creating an infix operator
|
||||||
|
| method to use with our infix type""") {
|
||||||
|
|
||||||
|
case class Person(name: String) {
|
||||||
|
def loves(person: Person) = new Loves(this, person)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Loves[A, B](val a: A, val b: B)
|
||||||
|
|
||||||
|
def announceCouple(couple: Person Loves Person) = {
|
||||||
|
//Notice our type: Person loves Person!
|
||||||
|
couple.a.name + " is in love with " + couple.b.name
|
||||||
|
}
|
||||||
|
|
||||||
|
val romeo = new Person("Romeo")
|
||||||
|
val juliet = new Person("Juliet")
|
||||||
|
|
||||||
|
announceCouple(romeo loves juliet) should be("Romeo is in love with Juliet")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
|
||||||
|
class AboutInteroperability extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("""You can interop with a java class and it's use of collections by importing
|
||||||
|
| scala.collection.JavaConversions and letting scala implicitly convert from a Scala collection type
|
||||||
|
| into a Java collection type. See AboutImplicits Koan Suite for more details and see src/test/java for the
|
||||||
|
| SomeJavaClass file. This koan
|
||||||
|
| converts a scala List of String to java List of raw type.""") {
|
||||||
|
|
||||||
|
import scala.collection.JavaConversions._
|
||||||
|
val d = new SomeJavaClass
|
||||||
|
val e = List("one", "two", "three")
|
||||||
|
d.findSizeOfRawType(e) should be(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Boat(size: Int, manufacturer: String)
|
||||||
|
|
||||||
|
koan("""This koan converts a scala List of Boat (our own class) to java List of unknown <?> type.""") {
|
||||||
|
import scala.collection.JavaConversions._
|
||||||
|
val d = new SomeJavaClass
|
||||||
|
val e = List(new Boat(33, "Skyway"), new Boat(35, "New Boat"))
|
||||||
|
d.findSizeOfUnknownType(e) should be(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutIterables extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("""Iterable is a trait that has the ability to return an iterator of itself.
|
||||||
|
| Some known iterators are Sets, Lists, Vectors, Stacks, and Streams. Iterator has two
|
||||||
|
| important methods: `hasNext`, which answers whether the iterator has another element
|
||||||
|
| available. `next` which will return the next element in the iterator.""") {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21)
|
||||||
|
val it = list.iterator
|
||||||
|
if (it.hasNext) {
|
||||||
|
it.next should be(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`grouped` will return an fixed sized Iterable chucks of an Iterable""") {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21, 24, 32)
|
||||||
|
val it = list grouped 3
|
||||||
|
it.next() should be(List(3, 5, 9))
|
||||||
|
it.next() should be(List(11, 15, 19))
|
||||||
|
it.next() should be(List(21, 24, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`sliding` will return an Iterable that shows a sliding window of an Iterable.""") {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21, 24, 32)
|
||||||
|
val it = list sliding 3
|
||||||
|
it.next() should be(List(3, 5, 9))
|
||||||
|
it.next() should be(List(5, 9, 11))
|
||||||
|
it.next() should be(List(9, 11, 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`sliding` can take the size of the window as well the size of the step during each
|
||||||
|
| iteration""") {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21, 24, 32)
|
||||||
|
val it = list sliding (3, 3)
|
||||||
|
it.next() should be(List(3, 5, 9))
|
||||||
|
it.next() should be(List(11, 15, 19))
|
||||||
|
it.next() should be(List(21, 24, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`takeRight` is the opposite of 'take' in Traversable. It retrieves the last elements
|
||||||
|
| of an Iterable. """) {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21, 24, 32)
|
||||||
|
(list takeRight 3) should be(List(21, 24, 32))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`dropRight` will drop the number of elements from the right. """) {
|
||||||
|
val list = List(3, 5, 9, 11, 15, 19, 21, 24, 32)
|
||||||
|
(list dropRight 3) should be(List(3, 5, 9, 11, 15, 19))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`zip` will stitch two iterables into an iterable of pairs of corresponding elements
|
||||||
|
| from both iterables. e.g. Iterable(x1, x2, x3) zip Iterable(y1, y2, y3) will
|
||||||
|
| return ((x1,y1), (x2, y2), (x3, y3))""") {
|
||||||
|
val xs = List(3, 5, 9)
|
||||||
|
val ys = List("Bob", "Ann", "Stella")
|
||||||
|
(xs zip ys) should be(List((3, "Bob"), (5, "Ann"), (9, "Stella")))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""if two Iterables aren't the same size, then `zip` will only zip what can only be paired.
|
||||||
|
| e.g. Iterable(x1, x2, x3) zip Iterable(y1, y2) will
|
||||||
|
| return ((x1,y1), (x2, y2))""") {
|
||||||
|
val xs = List(3, 5, 9)
|
||||||
|
val ys = List("Bob", "Ann")
|
||||||
|
(xs zip ys) should be(List((3, "Bob"), (5, "Ann")))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""if two Iterables aren't the same size, then `zipAll` can provide fillers for what it couldn't
|
||||||
|
| find a complement for. e.g. Iterable(x1, x2, x3) zipAll (Iterable(y1, y2), x, y) will
|
||||||
|
| return ((x1,y1), (x2, y2, y))""") {
|
||||||
|
val xs = List(3, 5, 9)
|
||||||
|
val ys = List("Bob", "Ann")
|
||||||
|
(xs zipAll (ys, -1, "?")) should be(List((3, "Bob"), (5, "Ann"), (9, "?")))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`zipWithIndex` will zip an Iterable with it's integer index""") {
|
||||||
|
val xs = List("Manny", "Moe", "Jack")
|
||||||
|
xs.zipWithIndex should be(List(("Manny", 0), ("Moe", 1), ("Jack", 2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`sameElements` will return true if the two iterables have the same number of elements""") {
|
||||||
|
val xs = List("Manny", "Moe", "Jack")
|
||||||
|
val ys = List("Manny", "Moe", "Jack")
|
||||||
|
(xs sameElements ys) should be (true)
|
||||||
|
|
||||||
|
val xs1 = Set(3,2,1,4,5,6,7)
|
||||||
|
val ys1 = Set(7,2,1,4,5,6,3)
|
||||||
|
(xs1 sameElements ys1) should be (true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutLazySequences extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Creating a lazy collection form a strict collection") {
|
||||||
|
val strictList = List(10, 20, 30)
|
||||||
|
val lazyList = strictList.view
|
||||||
|
lazyList.head should be(strictList.head)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Strict collection always processes it elements but lazy collection does on demand") {
|
||||||
|
var x = 0
|
||||||
|
def inc = {
|
||||||
|
x += 1;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
var strictList = List(inc _, inc _, inc _)
|
||||||
|
strictList.map(f => f()).head should be(1)
|
||||||
|
x should be(3)
|
||||||
|
strictList.map(f => f()).head
|
||||||
|
x should be(6)
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
val lazyList = strictList.view
|
||||||
|
lazyList.map(f => f()).head should be(1)
|
||||||
|
x should be(1)
|
||||||
|
lazyList.map(f => f()).head should be(2)
|
||||||
|
x should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lazy collection sometimes avoid processing errors") {
|
||||||
|
val lazyList = List(2, -2, 0, 4).view map {
|
||||||
|
2 / _
|
||||||
|
}
|
||||||
|
lazyList.head should be(1)
|
||||||
|
lazyList(1) should be(-1)
|
||||||
|
intercept[ArithmeticException] {
|
||||||
|
lazyList(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lazy collections could also be infinite") {
|
||||||
|
val infinite = Stream.from(1)
|
||||||
|
infinite.take(4).sum should be(10)
|
||||||
|
Stream.continually(1).take(4).sum should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Always remember tail of a lazy collection is never computed unless required") {
|
||||||
|
def makeLazy(value: Int): Stream[Int] = {
|
||||||
|
Stream.cons(value, makeLazy(value + 1))
|
||||||
|
}
|
||||||
|
val stream = makeLazy(1)
|
||||||
|
stream.head should be(1)
|
||||||
|
stream.tail.head should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
130
src/test/scala/org/functionalkoans/forscala/AboutLists.scala
Normal file
130
src/test/scala/org/functionalkoans/forscala/AboutLists.scala
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutLists extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Nil lists are identical, even of different types") {
|
||||||
|
val a: List[String] = Nil
|
||||||
|
val b: List[Int] = Nil
|
||||||
|
|
||||||
|
(a == Nil) should be(true)
|
||||||
|
(b == Nil) should be(true)
|
||||||
|
(a == b) should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists are easily created") {
|
||||||
|
val a = List(1, 2, 3)
|
||||||
|
a should equal(List(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Eq tests identity (same object)") {
|
||||||
|
val a = List(1, 2, 3)
|
||||||
|
val b = List(1, 2, 3)
|
||||||
|
|
||||||
|
|
||||||
|
(a eq b) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists can be accessed via head and tail") {
|
||||||
|
val a = List(1, 2, 3)
|
||||||
|
a.head should equal(1)
|
||||||
|
a.tail should equal(List(2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists can be accessed at random") {
|
||||||
|
val a = List(1, 3, 5, 7, 9)
|
||||||
|
a(0) should equal(1)
|
||||||
|
a(1) should equal(3)
|
||||||
|
a(4) should equal(9)
|
||||||
|
intercept[IndexOutOfBoundsException] {
|
||||||
|
println(a(5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists are immutable") {
|
||||||
|
val a = List(1, 3, 5, 7, 9)
|
||||||
|
val b = a.filterNot(v => v == 5) // remove where value is 5
|
||||||
|
|
||||||
|
a should equal(List(1, 3, 5, 7, 9))
|
||||||
|
b should equal(List(1, 3, 7, 9))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists have many useful methods") {
|
||||||
|
val a = List(1, 3, 5, 7, 9)
|
||||||
|
|
||||||
|
// get the length of the list
|
||||||
|
a.length should equal(5)
|
||||||
|
|
||||||
|
// reverse the list
|
||||||
|
a.reverse should equal(List(9, 7, 5, 3, 1))
|
||||||
|
|
||||||
|
// convert the list to a string representation
|
||||||
|
a.toString should equal("List(1, 3, 5, 7, 9)")
|
||||||
|
|
||||||
|
// map a function to double the numbers over the list
|
||||||
|
a.map {
|
||||||
|
v => v * 2
|
||||||
|
} should equal(List(2, 6, 10, 14, 18))
|
||||||
|
|
||||||
|
// filter out any values divisible by 3 in the list
|
||||||
|
a.filter {
|
||||||
|
v => v % 3 == 0
|
||||||
|
} should equal(List(3, 9))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Functions over lists can use _ as shorthand") {
|
||||||
|
val a = List(1, 2, 3)
|
||||||
|
a.map {
|
||||||
|
_ * 2
|
||||||
|
} should equal(List(2, 4, 6))
|
||||||
|
a.filter {
|
||||||
|
_ % 2 == 0
|
||||||
|
} should equal(List(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Functions over lists can use () instead of {}") {
|
||||||
|
val a = List(1, 2, 3)
|
||||||
|
a.map(_ * 2) should equal(List(2, 4, 6))
|
||||||
|
a.filter(_ % 2 != 0) should equal(List(1, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists can be 'reduced' with a mathematical operation") {
|
||||||
|
val a = List(1, 3, 5, 7)
|
||||||
|
// note the two _s below indicate the first and second args respectively
|
||||||
|
a.reduceLeft(_ + _) should equal(16)
|
||||||
|
a.reduceLeft(_ * _) should equal(105)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Foldleft is like reduce, but with an explicit starting value") {
|
||||||
|
val a = List(1, 3, 5, 7)
|
||||||
|
// foldLeft uses a form called currying that we will explore later
|
||||||
|
a.foldLeft(0)(_ + _) should equal(16)
|
||||||
|
a.foldLeft(10)(_ + _) should equal(26)
|
||||||
|
a.foldLeft(1)(_ * _) should equal(105)
|
||||||
|
a.foldLeft(0)(_ * _) should equal(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can create a list from a range") {
|
||||||
|
val a = (1 to 5).toList
|
||||||
|
a should be(List(1, 2, 3, 4, 5))
|
||||||
|
val b = (1 until 5).toList
|
||||||
|
b should be(List(1, 2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Lists reuse their tails") {
|
||||||
|
val d = Nil
|
||||||
|
val c = 3 :: d
|
||||||
|
val b = 2 :: c
|
||||||
|
val a = 1 :: b
|
||||||
|
|
||||||
|
a should be(List(1, 2, 3))
|
||||||
|
a.tail should be(List(2, 3))
|
||||||
|
b.tail should be(List(3))
|
||||||
|
c.tail should be(List())
|
||||||
|
}
|
||||||
|
|
||||||
|
//For more about what lists can do, visit AboutTraversables.scala koans
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutLiteralBooleans extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""Boolean literals are either true or false, using the true or false keyword""") {
|
||||||
|
val a = true
|
||||||
|
val b = false
|
||||||
|
val c = 1 > 2
|
||||||
|
val d = 1 < 2
|
||||||
|
val e = a == c
|
||||||
|
val f = b == d
|
||||||
|
a should be(true)
|
||||||
|
b should be(false)
|
||||||
|
c should be(false)
|
||||||
|
d should be(true)
|
||||||
|
e should be(false)
|
||||||
|
f should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutLiteralNumbers extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("Integer Literals are 32-bit and can be created from decimal, octal, or hexadecimal") {
|
||||||
|
val a = 2
|
||||||
|
val b = 31
|
||||||
|
val c = 0x30F
|
||||||
|
val d = 077
|
||||||
|
val e = 0
|
||||||
|
val f = -2
|
||||||
|
val g = -31
|
||||||
|
val h = -0x30F
|
||||||
|
val i = -077
|
||||||
|
a should be(2)
|
||||||
|
b should be(31)
|
||||||
|
c should be(783) //Hint: 30F = 783
|
||||||
|
d should be(63) //Hint: 077 = 63
|
||||||
|
e should be(0)
|
||||||
|
f should be(-2)
|
||||||
|
g should be(-31)
|
||||||
|
h should be(-783) //Hint: 30F = 783
|
||||||
|
i should be(-63) //Hint: 077 = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Long Literals are 64 bit, are specified by appending an L or l at the end;
|
||||||
|
| l is rarely used since it looks like a 1""") {
|
||||||
|
val a = 2L
|
||||||
|
val b = 31L
|
||||||
|
val c = 0x30FL
|
||||||
|
val d = 077L
|
||||||
|
val e = 0L
|
||||||
|
val f = -2l
|
||||||
|
val g = -31L
|
||||||
|
val h = -0x30FL
|
||||||
|
val i = -077L
|
||||||
|
|
||||||
|
a should be(2)
|
||||||
|
b should be(31)
|
||||||
|
c should be(783) //Hint: 30F = 783
|
||||||
|
d should be(63) //Hint: 077 = 63
|
||||||
|
e should be(0)
|
||||||
|
f should be(-2)
|
||||||
|
g should be(-31)
|
||||||
|
h should be(-783) //Hint: 30F = 783
|
||||||
|
i should be(-63) //Hint: 077 = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Float and Double Literals are IEEE 754 for specific,
|
||||||
|
| Float are 32-bit length, Doubles are 64-bit.
|
||||||
|
| Floats can be coerced using a f or F suffix, and
|
||||||
|
| Doubles can be coerced using a d or D suffix.
|
||||||
|
| Exponent are specified using e or E.""") {
|
||||||
|
|
||||||
|
val a = 3.0
|
||||||
|
val b = 3.00
|
||||||
|
val c = 2.73
|
||||||
|
val d = 3f
|
||||||
|
val e = 3.22d
|
||||||
|
val f = 93e-9
|
||||||
|
val g = 93E-9
|
||||||
|
val h = 0.0
|
||||||
|
val i = 9.23E-9D
|
||||||
|
|
||||||
|
a should be(3.0)
|
||||||
|
b should be(3.0)
|
||||||
|
c should be(2.73)
|
||||||
|
d should be(3.0)
|
||||||
|
e should be(3.22)
|
||||||
|
f should be(93e-9)
|
||||||
|
g should be(93E-9)
|
||||||
|
h should be(0.0)
|
||||||
|
i should be(9.23E-9D)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Trick: To distinguish the dot for a method invocation from the
|
||||||
|
| decimal point in a float or double literal,
|
||||||
|
| add a space after the literal""") {
|
||||||
|
3.0.toString should be("3.0")
|
||||||
|
3.toString should be("3")
|
||||||
|
(3. toString) should be("3.0")
|
||||||
|
(3.0 toString) should be("3.0")
|
||||||
|
3d.toString should be("3.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutLiteralStrings extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Character Literals are quoted with single quotes") {
|
||||||
|
val a = 'a'
|
||||||
|
val b = 'B'
|
||||||
|
|
||||||
|
a.toString should be("a")
|
||||||
|
b.toString should be("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Character Literals can use hexadecimal Unicode") {
|
||||||
|
val c = '\u0061' //unicode for a
|
||||||
|
|
||||||
|
c.toString should be("a")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Character Literals can use octal as well") {
|
||||||
|
val d = '\141' //octal for a
|
||||||
|
|
||||||
|
d.toString should be("a")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Character Literals can use escape sequences") {
|
||||||
|
val e = '\"'
|
||||||
|
val f = '\\'
|
||||||
|
|
||||||
|
e.toString should be("\"")
|
||||||
|
f.toString should be("\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("One-Line String Literals are surrounded by quotation marks.") {
|
||||||
|
val a = "To be or not to be"
|
||||||
|
a should be("To be or not to be")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("String Literals can contain escape sequences.") {
|
||||||
|
val a = "An \141pple \141 d\141y keeps the doctor \141w\141y"
|
||||||
|
a should be("An apple a day keeps the doctor away")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Multiline String literals
|
||||||
|
are surrounded
|
||||||
|
by three quotation marks""") {
|
||||||
|
val a = """An apple a day
|
||||||
|
keeps the doctor away"""
|
||||||
|
a.split('\n').size should be(2) //a.split('\n').size determines the number of lines
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Use stripMargin to prettify multi-line strings") {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Multiline String literals can use | to specify the starting position
|
||||||
|
* of subsequent lines, then use stripMargin to remove the surplus indentation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
val a = """An apple a day
|
||||||
|
|keeps the doctor away"""
|
||||||
|
a.stripMargin.split('\n')(1).charAt(0) should be('k')
|
||||||
|
|
||||||
|
/*
|
||||||
|
* a.stripMargin.split('\n')(1).charAt(0)
|
||||||
|
* gets the first character of the second line
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala
Normal file
138
src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutLiterals extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("Integer Literals are 32-bit and can be created from decimal, octal, or hexadecimal") {
|
||||||
|
val a = 2
|
||||||
|
val b = 31
|
||||||
|
val c = 0x30F
|
||||||
|
val d = 077
|
||||||
|
val e = 0
|
||||||
|
val f = -2
|
||||||
|
val g = -31
|
||||||
|
val h = -0x30F
|
||||||
|
val i = -077
|
||||||
|
a should be(2)
|
||||||
|
b should be(31)
|
||||||
|
c should be(783) //Hint: 30F = 783
|
||||||
|
d should be(63) //Hint: 077 = 63
|
||||||
|
e should be(0)
|
||||||
|
f should be(-2)
|
||||||
|
g should be(-31)
|
||||||
|
h should be(-783) //Hint: 30F = 783
|
||||||
|
i should be(-63) //Hint: 077 = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Long Literals are 64 bit, are specified by appending an L or l at the end;
|
||||||
|
| l is rarely used since it looks like a 1""") {
|
||||||
|
val a = 2L
|
||||||
|
val b = 31L
|
||||||
|
val c = 0x30FL
|
||||||
|
val d = 077L
|
||||||
|
val e = 0L
|
||||||
|
val f = -2l
|
||||||
|
val g = -31L
|
||||||
|
val h = -0x30FL
|
||||||
|
val i = -077L
|
||||||
|
|
||||||
|
a should be(2)
|
||||||
|
b should be(31)
|
||||||
|
c should be(783) //Hint: 30F = 783
|
||||||
|
d should be(63) //Hint: 077 = 73
|
||||||
|
e should be(0)
|
||||||
|
f should be(-2)
|
||||||
|
g should be(-31)
|
||||||
|
h should be(-783) //Hint: 30F = 783
|
||||||
|
i should be(-63) //Hint: 077 = 63
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Float and Double Literals are IEEE 754 for specific,
|
||||||
|
| Float are 32-bit length, Doubles are 64-bit.
|
||||||
|
| Floats can be coerced using a f or F suffix, and
|
||||||
|
| Doubles can be coerced using a d or D suffix.
|
||||||
|
| Exponent are specified using e or E.""") {
|
||||||
|
|
||||||
|
val a = 3.0
|
||||||
|
val b = 3.00
|
||||||
|
val c = 2.73
|
||||||
|
val d = 3f
|
||||||
|
val e = 3.22d
|
||||||
|
val f = 93e-9
|
||||||
|
val g = 93E-9
|
||||||
|
val h = 0.0
|
||||||
|
val i = 9.23E-9D
|
||||||
|
|
||||||
|
a should be(3.0)
|
||||||
|
b should be(3.00)
|
||||||
|
c should be(2.73)
|
||||||
|
d should be(3.0)
|
||||||
|
e should be(3.22)
|
||||||
|
f should be(93e-9)
|
||||||
|
g should be(93E-9)
|
||||||
|
h should be(0)
|
||||||
|
i should be(9.23E-9D)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Trick: To get a string representation of the
|
||||||
|
| float or double literal add a space after the literal""") {
|
||||||
|
3.0.toString should be("3.0")
|
||||||
|
3.toString should be("3")
|
||||||
|
(3. toString) should be("3.0")
|
||||||
|
(3.0 toString) should be("3.0")
|
||||||
|
3d.toString should be("3.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Boolean literals are either true or false, using the true or false keyword""") {
|
||||||
|
val a = true
|
||||||
|
val b = false
|
||||||
|
a should be(true)
|
||||||
|
b should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Character Literals can either be an a single character,
|
||||||
|
| an escape sequence, a Unicode octal up to 255 or a hexadecimal""") {
|
||||||
|
val a = 'a'
|
||||||
|
val b = 'B'
|
||||||
|
val c = '\u0061' //unicode for a
|
||||||
|
val d = '\141' //octal for a
|
||||||
|
val e = '\"'
|
||||||
|
val f = '\\'
|
||||||
|
|
||||||
|
//format(a) is a string format, meaning the "%c".format(x)
|
||||||
|
//will return the string representation of the char.
|
||||||
|
|
||||||
|
"%c".format(a) should be("a")
|
||||||
|
"%c".format(b) should be("B")
|
||||||
|
"%c".format(c) should be("a")
|
||||||
|
"%c".format(d) should be("a")
|
||||||
|
"%c".format(e) should be("\"")
|
||||||
|
"%c".format(f) should be("\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""One-Line String Literals are surrounded by quotation marks.""") {
|
||||||
|
val a = "To be or not to be"
|
||||||
|
a should be("To be or not to be")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""String Literals can contain escape sequences.""") {
|
||||||
|
val a = "An \141pple \141 d\141y keeps the doctor \141w\141y"
|
||||||
|
a should be("An apple a day keeps the doctor away")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Multiline String literals are surrounded by three quotation marks""") {
|
||||||
|
val a = """An apple a day
|
||||||
|
keeps the doctor away"""
|
||||||
|
a.split('\n').size should be(2) //a.split('\n').size determines the number of lines
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Multiline String literals on subsequent lines can have | to specify
|
||||||
|
| the start of the line, then use stripMargin to display it correctly""") {
|
||||||
|
val a = """An apple a day
|
||||||
|
|keeps the doctor away"""
|
||||||
|
a.stripMargin.split('\n')(1).charAt(0) should be('k') //a.stripMargin.split('\n')(1).charAt(0)
|
||||||
|
//gets the first character on the 2nd line
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Daniel Hinojosa
|
||||||
|
* User: Daniel Hinojosa
|
||||||
|
* Date: 3/6/11
|
||||||
|
* Time: 9:50 PM
|
||||||
|
* url: <a href="http://www.evolutionnext.com">http://www.evolutionnext.com</a>
|
||||||
|
* email: <a href="mailto:dhinojosa@evolutionnext.com">dhinojosa@evolutionnext.com</a>
|
||||||
|
* tel: 505.363.5832
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Monkey
|
||||||
|
|
||||||
|
|
||||||
|
class AboutManifests extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("""Manifests can be used to determine a type used
|
||||||
|
| before it erased by the VM by using an implicit manifest argument.""") {
|
||||||
|
def inspect[T](l: List[T])(implicit manifest: scala.reflect.Manifest[T]) = manifest.toString
|
||||||
|
val list = 1 :: 2 :: 3 :: 4 :: 5 :: Nil
|
||||||
|
inspect(list) should be("Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Manifests can be attached to classes. Manifests have other meta-information about
|
||||||
|
| the type erased""") {
|
||||||
|
class Barrel[T](implicit m: scala.reflect.Manifest[T]) {
|
||||||
|
def +(t: T) = "1 %s has been added".format(m.erasure.getSimpleName) //Simple-name of the class erased
|
||||||
|
}
|
||||||
|
val monkeyBarrel = new Barrel[Monkey]
|
||||||
|
(monkeyBarrel + new Monkey) should be("1 Monkey has been added")
|
||||||
|
}
|
||||||
|
}
|
||||||
139
src/test/scala/org/functionalkoans/forscala/AboutMaps.scala
Normal file
139
src/test/scala/org/functionalkoans/forscala/AboutMaps.scala
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class AboutMaps extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Maps can be created easily") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Maps contain distinct pairings") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "MI" -> "Michigan")
|
||||||
|
myMap.size should be(3)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Maps can be added to easily") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "MI" -> "Michigan")
|
||||||
|
|
||||||
|
val aNewMap = myMap + ("IL" -> "Illinois")
|
||||||
|
|
||||||
|
aNewMap.contains("IL") should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map values can be iterated") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "MI" -> "Michigan")
|
||||||
|
|
||||||
|
val mapValues = myMap.values
|
||||||
|
|
||||||
|
mapValues.size should be(3)
|
||||||
|
|
||||||
|
mapValues.head should be("Michigan")
|
||||||
|
|
||||||
|
val lastElement = mapValues.last
|
||||||
|
lastElement should be("Wisconsin")
|
||||||
|
|
||||||
|
// for (mval <- mapValues) println(mval)
|
||||||
|
|
||||||
|
// NOTE that the following will not compile, as iterators do not implement "contains"
|
||||||
|
//mapValues.contains("Illinois") should be (true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Maps insertion with duplicate key updates previous entry with subsequent value") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "MI" -> "Meechigan")
|
||||||
|
|
||||||
|
val mapValues = myMap.values
|
||||||
|
|
||||||
|
mapValues.size should be(3)
|
||||||
|
|
||||||
|
myMap("MI") should be("Meechigan")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map keys may be of mixed type") {
|
||||||
|
val myMap = Map("Ann Arbor" -> "MI", 49931 -> "MI")
|
||||||
|
myMap("Ann Arbor") should be("MI")
|
||||||
|
myMap(49931) should be("MI")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mixed type values can be added to a map ") {
|
||||||
|
val myMap = scala.collection.mutable.Map.empty[String, Any]
|
||||||
|
myMap("Ann Arbor") = (48103, 48104, 48108)
|
||||||
|
myMap("Houghton") = 49931
|
||||||
|
|
||||||
|
myMap("Houghton") should be(49931)
|
||||||
|
myMap("Ann Arbor") should be((48103, 48104, 48108))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// what happens if you change the Any to Int
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("Maps may be accessed") {
|
||||||
|
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap("MI") should be("Michigan")
|
||||||
|
myMap("IA") should be("Iowa")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map elements can be removed easily") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
val aNewMap = myMap - "MI"
|
||||||
|
aNewMap.contains("MI") should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Accessing a map by key results in an exception if key is not found") {
|
||||||
|
|
||||||
|
val myMap = Map("OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
|
||||||
|
intercept[NoSuchElementException] {
|
||||||
|
myMap("MI") should be("Michigan")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map elements can be removed in multiple") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
|
||||||
|
|
||||||
|
val aNewMap = myMap -- List("MI", "OH")
|
||||||
|
|
||||||
|
aNewMap.contains("MI") should be(false)
|
||||||
|
|
||||||
|
aNewMap.contains("WI") should be(true)
|
||||||
|
aNewMap.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map elements can be removed with a tuple") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
val aNewMap = myMap - ("MI", "WI") // Notice: single '-' operator for tuples
|
||||||
|
|
||||||
|
aNewMap.contains("MI") should be(false)
|
||||||
|
aNewMap.contains("OH") should be(true)
|
||||||
|
aNewMap.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Attempted removal of nonexistent elements from a map is handled gracefully") {
|
||||||
|
val myMap = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
val aNewMap = myMap - "MN"
|
||||||
|
|
||||||
|
aNewMap.equals(myMap) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Map equivalency is independent of order") {
|
||||||
|
|
||||||
|
val myMap1 = Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
val myMap2 = Map("WI" -> "Wisconsin", "MI" -> "Michigan", "IA" -> "Iowa", "OH" -> "Ohio")
|
||||||
|
|
||||||
|
|
||||||
|
myMap1.equals(myMap2) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
class AboutMutableMaps extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Mutable maps can be created easily") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap.size should be(4)
|
||||||
|
myMap += "OR" -> "Oregon"
|
||||||
|
myMap contains "OR" should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can have elements removed") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap -= "OH"
|
||||||
|
myMap contains "OH" should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can have tuples of elements removed") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap -= ("IA", "OH")
|
||||||
|
myMap contains "OH" should be(false)
|
||||||
|
myMap.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can have tuples of elements added") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "WI" -> "Wisconsin")
|
||||||
|
myMap += ("IA" -> "Iowa", "OH" -> "Ohio")
|
||||||
|
myMap contains "OH" should be(true)
|
||||||
|
myMap.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can have Lists of elements added") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "WI" -> "Wisconsin")
|
||||||
|
myMap ++= List("IA" -> "Iowa", "OH" -> "Ohio")
|
||||||
|
myMap contains "OH" should be(true)
|
||||||
|
myMap.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can have Lists of elements removed") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap --= List("IA", "OH")
|
||||||
|
myMap contains "OH" should be(false)
|
||||||
|
myMap.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable maps can be cleared") {
|
||||||
|
val myMap = mutable.Map("MI" -> "Michigan", "OH" -> "Ohio", "WI" -> "Wisconsin", "IA" -> "Iowa")
|
||||||
|
myMap.clear() // Convention is to use parens if possible when method called changes state
|
||||||
|
myMap contains "OH" should be(false)
|
||||||
|
myMap.size should be(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
class AboutMutableSets extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Mutable sets can be created easily") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet.size should be(4)
|
||||||
|
mySet += "Oregon"
|
||||||
|
mySet contains "Oregon" should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can have elements removed") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet -= "Ohio"
|
||||||
|
mySet contains "Ohio" should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can have tuples of elements removed") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet -= ("Iowa", "Ohio")
|
||||||
|
mySet contains "Ohio" should be(false)
|
||||||
|
mySet.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can have tuples of elements added") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Wisconsin")
|
||||||
|
mySet += ("Iowa", "Ohio")
|
||||||
|
mySet contains "Ohio" should be(true)
|
||||||
|
mySet.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can have Lists of elements added") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Wisconsin")
|
||||||
|
mySet ++= List("Iowa", "Ohio")
|
||||||
|
mySet contains "Ohio" should be(true)
|
||||||
|
mySet.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can have Lists of elements removed") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet --= List("Iowa", "Ohio")
|
||||||
|
mySet contains "Ohio" should be(false)
|
||||||
|
mySet.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Mutable sets can be cleared") {
|
||||||
|
val mySet = mutable.Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet.clear() // Convention is to use parens if possible when method called changes state
|
||||||
|
mySet contains "Ohio" should be(false)
|
||||||
|
mySet.size should be(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.BlankValues.__
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutNamedAndDefaultArguments() extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class WithoutClassParameters() {
|
||||||
|
def addColors(red: Int, green: Int, blue: Int) = {
|
||||||
|
(red, green, blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
def addColorsWithDefaults(red: Int = 0, green: Int = 0, blue: Int = 0) = {
|
||||||
|
(red, green, blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WithClassParameters(val defaultRed: Int, val defaultGreen: Int, val defaultBlue: Int) {
|
||||||
|
def addColors(red: Int, green: Int, blue: Int) = {
|
||||||
|
(red + defaultRed, green + defaultGreen, blue + defaultBlue)
|
||||||
|
}
|
||||||
|
|
||||||
|
def addColorsWithDefaults(red: Int = 0, green: Int = 0, blue: Int = 0) = {
|
||||||
|
(red + defaultRed, green + defaultGreen, blue + defaultBlue)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class WithClassParametersInClassDefinition(val defaultRed: Int = 0, val defaultGreen: Int = 255, val defaultBlue: Int = 100) {
|
||||||
|
def addColors(red: Int, green: Int, blue: Int) = {
|
||||||
|
(red + defaultRed, green + defaultGreen, blue + defaultBlue)
|
||||||
|
}
|
||||||
|
|
||||||
|
def addColorsWithDefaults(red: Int = 0, green: Int = 0, blue: Int = 0) = {
|
||||||
|
(red + defaultRed, green + defaultGreen, blue + defaultBlue)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("can specify arguments in any order if you use their names") {
|
||||||
|
val me = new WithoutClassParameters()
|
||||||
|
|
||||||
|
// what happens if you change the order of these parameters (nothing)
|
||||||
|
val myColor = me.addColors(green = 0, red = 255, blue = 0)
|
||||||
|
|
||||||
|
// for koan, remove the values in the should equal
|
||||||
|
myColor should equal(255, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("can default arguments if you leave them off") {
|
||||||
|
val me = new WithoutClassParameters()
|
||||||
|
val myColor = me.addColorsWithDefaults(green = 255)
|
||||||
|
|
||||||
|
myColor should equal(0, 255, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("can access class parameters and specify arguments in any order if you use their names") {
|
||||||
|
val me = new WithClassParameters(40, 50, 60)
|
||||||
|
val myColor = me.addColors(green = 50, red = 60, blue = 40)
|
||||||
|
|
||||||
|
myColor should equal(100, 100, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("can access class parameters and default arguments if you leave them off") {
|
||||||
|
val me = new WithClassParameters(10, 20, 30)
|
||||||
|
val myColor = me.addColorsWithDefaults(green = 70)
|
||||||
|
|
||||||
|
myColor should equal(10, 90, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("can default class parameters and have default arguments too") {
|
||||||
|
val me = new WithClassParametersInClassDefinition()
|
||||||
|
val myColor = me.addColorsWithDefaults(green = 70)
|
||||||
|
|
||||||
|
myColor should equal(0, 325, 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("default parameters can be functional too") {
|
||||||
|
def reduce(a: Int, f: (Int, Int) => Int = (_ + _)): Int = f(a, a)
|
||||||
|
|
||||||
|
reduce(5) should equal(10)
|
||||||
|
reduce(5, _ * _) should equal(25)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
130
src/test/scala/org/functionalkoans/forscala/AboutOptions.scala
Normal file
130
src/test/scala/org/functionalkoans/forscala/AboutOptions.scala
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutOptions extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Option can have one of two values - Some or None") {
|
||||||
|
val someValue: Option[String] = Some("I am wrapped in something")
|
||||||
|
someValue.get should be("I am wrapped in something")
|
||||||
|
|
||||||
|
val nullValue: Option[String] = None
|
||||||
|
nullValue should be(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Represent null with None because null is a bad idea") {
|
||||||
|
val value1 = maybeItWillReturnSomething(true)
|
||||||
|
val value2 = maybeItWillReturnSomething(false)
|
||||||
|
|
||||||
|
value1.get should be("Found value")
|
||||||
|
intercept[java.util.NoSuchElementException] {
|
||||||
|
value2.get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Provide a default value for None") {
|
||||||
|
val value1 = maybeItWillReturnSomething(true)
|
||||||
|
val value2 = maybeItWillReturnSomething(false)
|
||||||
|
|
||||||
|
value1 getOrElse "No value" should be("Found value")
|
||||||
|
value2 getOrElse "No value" should be("No value")
|
||||||
|
value2 getOrElse {
|
||||||
|
"default function"
|
||||||
|
} should be("default function")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("checking whether option has value") {
|
||||||
|
val value1 = maybeItWillReturnSomething(true)
|
||||||
|
val value2 = maybeItWillReturnSomething(false)
|
||||||
|
|
||||||
|
value1.isEmpty should be(false)
|
||||||
|
value2.isEmpty should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Option can also be used with pattern matching") {
|
||||||
|
val someValue: Option[Double] = Some(20.0)
|
||||||
|
val value = someValue match {
|
||||||
|
case Some(v) => v
|
||||||
|
case None => 0.0
|
||||||
|
}
|
||||||
|
value should be(20.0)
|
||||||
|
val noValue: Option[Double] = None
|
||||||
|
val value1 = noValue match {
|
||||||
|
case Some(v) => v
|
||||||
|
case None => 0.0
|
||||||
|
}
|
||||||
|
value1 should be(0.0)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Option is more than just a replacement of null, its also a collection") {
|
||||||
|
Some(10) map {
|
||||||
|
_ + 10
|
||||||
|
} should be(Some(20))
|
||||||
|
Some(10) filter {
|
||||||
|
_ == 10
|
||||||
|
} should be(Some(10))
|
||||||
|
Some(Some(10)) flatMap {
|
||||||
|
_ map {
|
||||||
|
_ + 10
|
||||||
|
}
|
||||||
|
} should be(Some(20))
|
||||||
|
|
||||||
|
var newValue1 = 0
|
||||||
|
Some(20) foreach {
|
||||||
|
newValue1 = _
|
||||||
|
}
|
||||||
|
newValue1 should be(20)
|
||||||
|
|
||||||
|
var newValue2 = 0
|
||||||
|
None foreach {
|
||||||
|
newValue2 = _
|
||||||
|
}
|
||||||
|
newValue2 should be(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Using Option to avoid if checks for null") {
|
||||||
|
//the ugly version
|
||||||
|
def makeFullName(firstName: String, lastName: String) = {
|
||||||
|
if (firstName != null) {
|
||||||
|
if (lastName != null) {
|
||||||
|
firstName + " " + lastName
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeFullName("Nilanjan", "Raychaudhuri") should be("Nilanjan Raychaudhuri")
|
||||||
|
makeFullName("Nilanjan", null) should be(null)
|
||||||
|
//the pretty version
|
||||||
|
def makeFullNamePrettyVersion(firstName: Option[String], lastName: Option[String]) = {
|
||||||
|
firstName flatMap {
|
||||||
|
fname =>
|
||||||
|
lastName flatMap {
|
||||||
|
lname =>
|
||||||
|
Some(fname + " " + lname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
makeFullNamePrettyVersion(Some("Nilanjan"), Some("Raychaudhuri")) should be(Some("Nilanjan Raychaudhuri"))
|
||||||
|
makeFullNamePrettyVersion(Some("Nilanjan"), None) should be(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Using in for comprehension") {
|
||||||
|
val values = List(Some(10), Some(20), None, Some(15))
|
||||||
|
val newValues = for {
|
||||||
|
someValue <- values
|
||||||
|
value <- someValue
|
||||||
|
} yield value
|
||||||
|
newValues should be(List(10, 20, 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
def maybeItWillReturnSomething(flag: Boolean): Option[String] = {
|
||||||
|
if (flag) Some("Found value") else None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutParentClasses extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("Class heirarchy is linear, a class can only extend from one parent class") {
|
||||||
|
class Worker(firstName: String, lastName: String) {}
|
||||||
|
class Employee(firstName: String, lastName: String, employeeID: Long) extends Worker(firstName, lastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A class that extends from another is polymorphic") {
|
||||||
|
class Worker(val firstName: String, val lastName: String) {}
|
||||||
|
class Employee(override val firstName: String, override val lastName: String,
|
||||||
|
val employeeID: Long) extends Worker(firstName, lastName)
|
||||||
|
|
||||||
|
val me = new Employee("Name", "Yourself", 1233)
|
||||||
|
val worker: Worker = me
|
||||||
|
|
||||||
|
worker.firstName should be("Name")
|
||||||
|
worker.lastName should be("Yourself")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("An abstract class, as in Java, cannot be instantiated and only inherited") {
|
||||||
|
abstract class Worker(val firstName: String, val lastName: String) {}
|
||||||
|
|
||||||
|
//val worker = new Worker
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("An class can be placed inside an abstract class just like in java") {
|
||||||
|
abstract class Worker(val firstName: String, val lastName: String) {
|
||||||
|
|
||||||
|
class Assignment(val hours: Long) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutPartialFunctions extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""A partial function is a trait that when
|
||||||
|
| implemented can be used as building blocks to determine
|
||||||
|
| a solution. The trait PartialFunction requires that the
|
||||||
|
| the method isDefinedAt and apply be implemented.""") {
|
||||||
|
|
||||||
|
val doubleEvens: PartialFunction[Int, Int] = new PartialFunction[Int, Int] {
|
||||||
|
//States that this partial function will take on the task
|
||||||
|
def isDefinedAt(x: Int) = x % 2 == 0
|
||||||
|
|
||||||
|
//What we do if this does partial function matches
|
||||||
|
def apply(v1: Int) = v1 * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
val tripleOdds: PartialFunction[Int, Int] = new PartialFunction[Int, Int] {
|
||||||
|
def isDefinedAt(x: Int) = x % 2 != 0
|
||||||
|
|
||||||
|
def apply(v1: Int) = v1 * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
val whatToDo = doubleEvens orElse tripleOdds //Here we chain the partial functions together
|
||||||
|
|
||||||
|
whatToDo(3) should be(9)
|
||||||
|
whatToDo(4) should be(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Case statements are a quick way to create partial functions. When you create a case
|
||||||
|
| statement, the apply and isDefinedAt is created for you.""") {
|
||||||
|
|
||||||
|
//The case statements are called case statements with guards
|
||||||
|
val doubleEvens: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) == 0) => x * 2
|
||||||
|
}
|
||||||
|
val tripleOdds: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) != 0) => x * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
val whatToDo = doubleEvens orElse tripleOdds //Here we chain the partial functions together
|
||||||
|
whatToDo(3) should be(9)
|
||||||
|
whatToDo(4) should be(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""The result of partial functions can have an \'andThen\' function added to the end
|
||||||
|
| of the chain""") {
|
||||||
|
|
||||||
|
//These are called case statements with guards
|
||||||
|
val doubleEvens: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) == 0) => x * 2
|
||||||
|
}
|
||||||
|
val tripleOdds: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) != 0) => x * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
val addFive = (x: Int) => x + 5
|
||||||
|
val whatToDo = doubleEvens orElse tripleOdds andThen addFive //Here we chain the partial functions together
|
||||||
|
whatToDo(3) should be(14)
|
||||||
|
whatToDo(4) should be(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""The result of partial functions can have an \'andThen\' function added to the end
|
||||||
|
| of the chain used to continue onto another chain of logic""") {
|
||||||
|
|
||||||
|
val doubleEvens: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) == 0) => x * 2
|
||||||
|
}
|
||||||
|
val tripleOdds: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if ((x % 2) != 0) => x * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
val printEven: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if ((x % 2) == 0) => "Even"
|
||||||
|
}
|
||||||
|
val printOdd: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if ((x % 2) != 0) => "Odd"
|
||||||
|
}
|
||||||
|
|
||||||
|
val whatToDo = doubleEvens orElse tripleOdds andThen (printEven orElse printOdd)
|
||||||
|
|
||||||
|
whatToDo(3) should be("Odd")
|
||||||
|
whatToDo(4) should be("Even")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutPartiallyAppliedFunctions extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("""A partially applied function is a function that you do not apply any or all the
|
||||||
|
| arguments, creating another function. This partially applied function
|
||||||
|
| doesn't apply any arguments""") {
|
||||||
|
def sum(a: Int, b: Int, c: Int) = a + b + c
|
||||||
|
val sum3 = sum _
|
||||||
|
sum3(1, 9, 7) should be(17)
|
||||||
|
sum(4, 5, 6) should be(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Partially applied functions can replace any number of arguments""") {
|
||||||
|
def sum(a: Int, b: Int, c: Int) = a + b + c
|
||||||
|
val sumC = sum(1, 10, _: Int)
|
||||||
|
sumC(4) should be(15)
|
||||||
|
sum(4, 5, 6) should be(15)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutPatternMatching extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
//TODO:Lists
|
||||||
|
//TODO:Guards
|
||||||
|
|
||||||
|
koan("Pattern matching returns something") {
|
||||||
|
|
||||||
|
val stuff = "blue"
|
||||||
|
|
||||||
|
val myStuff = stuff match {
|
||||||
|
case "red" => println("RED"); 1
|
||||||
|
case "blue" => println("BLUE"); 2
|
||||||
|
case "green" => println("GREEN"); 3
|
||||||
|
case _ => println(stuff); 0
|
||||||
|
}
|
||||||
|
|
||||||
|
myStuff should be(2)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Pattern matching can return complex somethings") {
|
||||||
|
val stuff = "blue"
|
||||||
|
|
||||||
|
val myStuff = stuff match {
|
||||||
|
case "red" => (255, 0, 0)
|
||||||
|
case "green" => (0, 255, 0)
|
||||||
|
case "blue" => (0, 0, 255)
|
||||||
|
case _ => println(stuff); 0
|
||||||
|
}
|
||||||
|
|
||||||
|
myStuff should be(0, 0, 255)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Pattern matching can match complex expressions") {
|
||||||
|
def goldilocks(expr: Any) = expr match {
|
||||||
|
case ("porridge", "Papa") => "Papa eating porridge"
|
||||||
|
case ("porridge", "Mama") => "Mama eating porridge"
|
||||||
|
case ("porridge", "Baby") => "Baby eating porridge"
|
||||||
|
case _ => "what?"
|
||||||
|
}
|
||||||
|
|
||||||
|
goldilocks(("porridge", "Mama")) should be("Mama eating porridge")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Pattern matching can wildcard parts of expressions") {
|
||||||
|
def goldilocks(expr: Any) = expr match {
|
||||||
|
case ("porridge", _) => "eating"
|
||||||
|
case ("chair", "Mama") => "sitting"
|
||||||
|
case ("bed", "Baby") => "sleeping"
|
||||||
|
case _ => "what?"
|
||||||
|
}
|
||||||
|
|
||||||
|
goldilocks(("porridge", "Papa")) should be("eating")
|
||||||
|
goldilocks(("chair", "Mama")) should be("sitting")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Pattern matching can substitute parts of expressions") {
|
||||||
|
def goldilocks(expr: Any) = expr match {
|
||||||
|
case ("porridge", bear) => bear + " said someone's been eating my porridge"
|
||||||
|
case ("chair", bear) => bear + " said someone's been sitting in my chair"
|
||||||
|
case ("bed", bear) => bear + " sais someone's been sleeping in my bed"
|
||||||
|
case _ => "what?"
|
||||||
|
}
|
||||||
|
|
||||||
|
goldilocks(("porridge", "Papa")) should be("Papa said someone's been eating my porridge")
|
||||||
|
goldilocks(("chair", "Mama")) should be("Mama said someone's been sitting in my chair")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Pattern matching can done on regular expression groups") {
|
||||||
|
val EatingRegularExpression = """Eating Alert: bear=([^,]+),\s+source=(.+)""".r
|
||||||
|
val SittingRegularExpression = """Sitting Alert: bear=([^,]+),\s+source=(.+)""".r
|
||||||
|
val SleepingRegularExpression = """Sleeping Alert: bear=([^,]+),\s+source=(.+)""".r
|
||||||
|
|
||||||
|
def goldilocks(expr: String) = expr match {
|
||||||
|
case (EatingRegularExpression(bear, source)) => "%s said someone's been eating my %s".format(bear, source)
|
||||||
|
case (SittingRegularExpression(bear, source)) => "%s said someone's been sitting on my %s".format(bear, source)
|
||||||
|
case (SleepingRegularExpression(bear, source)) => "%s said someone's been sleeping in my %s".format(bear, source)
|
||||||
|
case _ => "what?"
|
||||||
|
}
|
||||||
|
|
||||||
|
goldilocks("Eating Alert: bear=Papa, source=porridge") should be("Papa said someone's been eating my porridge")
|
||||||
|
goldilocks("Sitting Alert: bear=Mama, source=chair") should be("Mama said someone's been sitting on my chair")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""A backquote can be used to refer to a stable variable in scope to create a case statement.
|
||||||
|
| This prevents what is called \'Variable Shadowing\'""") {
|
||||||
|
val foodItem = "porridge"
|
||||||
|
|
||||||
|
def goldilocks(expr: Any) = expr match {
|
||||||
|
case (`foodItem`, _) => "eating"
|
||||||
|
case ("chair", "Mama") => "sitting"
|
||||||
|
case ("bed", "Baby") => "sleeping"
|
||||||
|
case _ => "what?"
|
||||||
|
}
|
||||||
|
|
||||||
|
goldilocks(("porridge", "Papa")) should be("eating")
|
||||||
|
goldilocks(("chair", "Mama")) should be("sitting")
|
||||||
|
goldilocks(("porridge", "Cousin")) should be("eating")
|
||||||
|
goldilocks(("beer", "Cousin")) should be("what?")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A backquote can be used to refer to a method parameter as a stable variable to create a case statement.") {
|
||||||
|
|
||||||
|
def patternEquals(i: Int, j: Int) = j match {
|
||||||
|
case `i` => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
patternEquals(3, 3) should be(true)
|
||||||
|
patternEquals(7, 9) should be(false)
|
||||||
|
patternEquals(9, 9) should be(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.BlankValues.__
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutPreconditions extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class WithParameterRequirement(val myValue: Int) {
|
||||||
|
require(myValue != 0)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Violating preconditions throws an exception") {
|
||||||
|
intercept[IllegalArgumentException] {
|
||||||
|
|
||||||
|
|
||||||
|
val myInstance = new WithParameterRequirement(0) // put the value needed to cause IllegalArgumentException in place of the blank
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
koan("On precondition violation, intercept expects type of exception thrown") {
|
||||||
|
intercept[IllegalArgumentException] {
|
||||||
|
// put the exception that will be thrown in place of the blank
|
||||||
|
|
||||||
|
|
||||||
|
val myInstance = new WithParameterRequirement(0)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
34
src/test/scala/org/functionalkoans/forscala/AboutRange.scala
Normal file
34
src/test/scala/org/functionalkoans/forscala/AboutRange.scala
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutRange extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Range are not inclusive at end of range") {
|
||||||
|
val someNumbers = Range(0, 10)
|
||||||
|
|
||||||
|
someNumbers.size should be(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Range can specify increment") {
|
||||||
|
val someNumbers = Range(2, 10, 3)
|
||||||
|
|
||||||
|
someNumbers.size should be(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Range can indicate inclusion") {
|
||||||
|
val someNumbers = Range(0, 34, 2)
|
||||||
|
someNumbers.contains(33) should be(false)
|
||||||
|
someNumbers.contains(32) should be(true)
|
||||||
|
someNumbers.contains(34) should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Range can specify to include value") {
|
||||||
|
val someNumbers = Range(0, 34).inclusive
|
||||||
|
|
||||||
|
someNumbers.contains(34) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutSequencesAndArrays extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("A list can be converted to an array") {
|
||||||
|
val l = List(1, 2, 3)
|
||||||
|
val a = l.toArray
|
||||||
|
a should equal(Array(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Any sequence can be converted to a list") {
|
||||||
|
val a = Array(1, 2, 3)
|
||||||
|
val s = a.toSeq
|
||||||
|
val l = s.toList
|
||||||
|
l should equal(List(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can create a sequence from a for comprehension") {
|
||||||
|
val s = for (v <- 1 to 4) yield v
|
||||||
|
s.toList should be(List(1, 2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can create a sequence from a for comprehension with a condition") {
|
||||||
|
val s = for (v <- 1 to 10 if v % 3 == 0) yield v
|
||||||
|
s.toList should be(List(3, 6, 9))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can filter any sequence based on a predicate") {
|
||||||
|
val s = Seq("hello", "to", "you")
|
||||||
|
val filtered = s.filter(_.length > 2)
|
||||||
|
filtered should be(Seq("hello", "you"))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can also filter Arrays in the same way") {
|
||||||
|
val a = Array("hello", "to", "you", "again")
|
||||||
|
val filtered = a.filter(_.length > 3)
|
||||||
|
filtered should be(Array("hello", "again"))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can map values in a sequence through a function") {
|
||||||
|
val s = Seq("hello", "world")
|
||||||
|
val r = s map {
|
||||||
|
_.reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
r should be(Seq("olleh", "dlrow"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
130
src/test/scala/org/functionalkoans/forscala/AboutSets.scala
Normal file
130
src/test/scala/org/functionalkoans/forscala/AboutSets.scala
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class AboutSets extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Sets can be created easily") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
mySet.size should be(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Sets contain distinct values") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Michigan")
|
||||||
|
mySet.size should be(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Sets can be added to easily") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val aNewSet = mySet + "Illinois"
|
||||||
|
|
||||||
|
aNewSet.contains("Illinois") should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Sets may be of mixed type") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", 12)
|
||||||
|
|
||||||
|
mySet.contains(12) should be(true)
|
||||||
|
|
||||||
|
mySet.contains("MI") should be(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Sets may be accessed") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", 12)
|
||||||
|
|
||||||
|
mySet(12) should be(true)
|
||||||
|
mySet("MI") should be(false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Set elements can be removed easily") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val aNewSet = mySet - "Michigan"
|
||||||
|
|
||||||
|
aNewSet.contains("Michigan") should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Set elements can be removed in multiple") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val aNewSet = mySet -- List("Michigan", "Ohio")
|
||||||
|
|
||||||
|
aNewSet.contains("Michigan") should be(false)
|
||||||
|
aNewSet.contains("Wisconsin") should be(true)
|
||||||
|
aNewSet.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Set elements can be removed with a tuple") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val aNewSet = mySet - ("Michigan", "Ohio") // Notice: single '-' operator for tuples
|
||||||
|
|
||||||
|
aNewSet.contains("Michigan") should be(false)
|
||||||
|
aNewSet.contains("Wisconsin") should be(true)
|
||||||
|
aNewSet.size should be(2)
|
||||||
|
}
|
||||||
|
koan("Attempted removal of nonexistent elements from a set is handled gracefully") {
|
||||||
|
val mySet = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val aNewSet = mySet - "Minnesota"
|
||||||
|
|
||||||
|
aNewSet.equals(mySet) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Sets can be iterated easily") {
|
||||||
|
val mySet = Set(1, 3, 4, 9)
|
||||||
|
var sum = 0
|
||||||
|
for (i <- mySet)
|
||||||
|
sum = sum + i
|
||||||
|
|
||||||
|
sum should be(17)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Two sets can be intersected easily") {
|
||||||
|
val mySet1 = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val mySet2 = Set("Wisconsin", "Michigan", "Minnesota")
|
||||||
|
val aNewSet = mySet1 intersect mySet2
|
||||||
|
// NOTE: Scala 2.7 used **, deprecated for & or intersect in Scala 2.8
|
||||||
|
|
||||||
|
aNewSet.equals(Set("Michigan", "Wisconsin")) should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Two sets can be joined as their union easily") {
|
||||||
|
val mySet1 = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val mySet2 = Set("Wisconsin", "Michigan", "Minnesota")
|
||||||
|
val aNewSet = mySet1 union mySet2 // NOTE: You can also use the "|" operator
|
||||||
|
|
||||||
|
aNewSet.equals(Set("Michigan", "Wisconsin", "Ohio", "Iowa", "Minnesota")) should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A set is either a subset of another set or it isn't") {
|
||||||
|
val mySet1 = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val mySet2 = Set("Wisconsin", "Michigan", "Minnesota")
|
||||||
|
val mySet3 = Set("Wisconsin", "Michigan")
|
||||||
|
|
||||||
|
mySet2 subsetOf mySet1 should be(false)
|
||||||
|
mySet3 subsetOf mySet1 should be(true)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("The difference between two sets can be obtained easily") {
|
||||||
|
val mySet1 = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val mySet2 = Set("Wisconsin", "Michigan")
|
||||||
|
val aNewSet = mySet1 diff mySet2 // Note: you can use the "&~" operator if you *really* want to.
|
||||||
|
|
||||||
|
aNewSet.equals(Set("Ohio", "Iowa")) should be(true)
|
||||||
|
}
|
||||||
|
koan("Set equivalency is independent of order") {
|
||||||
|
val mySet1 = Set("Michigan", "Ohio", "Wisconsin", "Iowa")
|
||||||
|
val mySet2 = Set("Wisconsin", "Michigan", "Ohio", "Iowa")
|
||||||
|
|
||||||
|
mySet1.equals(mySet2) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
399
src/test/scala/org/functionalkoans/forscala/AboutTraits.scala
Normal file
399
src/test/scala/org/functionalkoans/forscala/AboutTraits.scala
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutTraits extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("A class can use the \'extends\' keyword to mixin a trait if it is the only relationship the class inherits") {
|
||||||
|
case class Event(name: String, source: Any)
|
||||||
|
|
||||||
|
trait EventListener {
|
||||||
|
def listen(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyListener extends EventListener {
|
||||||
|
def listen(event: Event) {
|
||||||
|
event match {
|
||||||
|
case Event("Moose Stampede", _) => println("An unfortunate moose stampede occured")
|
||||||
|
case _ => println("Nothing of importance occured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val evt = Event("Moose Stampede", this)
|
||||||
|
val myListener = new MyListener()
|
||||||
|
myListener.listen(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""A class can only \'extends\' from _one_ class or trait, any subsequent extension
|
||||||
|
| should use the keyword \'with\'""") {
|
||||||
|
|
||||||
|
case class Event(name: String, source: Any)
|
||||||
|
|
||||||
|
trait EventListener {
|
||||||
|
def listen(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class OurListener
|
||||||
|
|
||||||
|
class MyListener extends OurListener with EventListener {
|
||||||
|
def listen(event: Event) {
|
||||||
|
event match {
|
||||||
|
case Event("Woodchuck Stampede", _) => println("An unfortunate woodchuck stampede occured")
|
||||||
|
case _ => println("Nothing of importance occured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val evt = Event("Woodchuck Stampede", this)
|
||||||
|
val myListener = new MyListener()
|
||||||
|
myListener.listen(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Protip: For many; using \'extends\' gives the impression of a \'is-a\' relationship and many don't want to
|
||||||
|
| extend from a trait that doesn't follow that relationship. If a class
|
||||||
|
| has no need to extend from a parent class but you want to mixin one or more traits, and would rather use
|
||||||
|
| \'with\' with all traits instead of one trait with extends and the others with \'with\' you can
|
||||||
|
| \'extends\' from an Object so that all traits can be used with \'with\'""") {
|
||||||
|
|
||||||
|
case class Event(name: String, source: Any)
|
||||||
|
|
||||||
|
trait EventListener {
|
||||||
|
def listen(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait MouseListener {
|
||||||
|
def listen(mouseEvent: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyListener extends Object with MouseListener with EventListener {
|
||||||
|
//Nice line here
|
||||||
|
def listen(event: Event) {
|
||||||
|
event match {
|
||||||
|
case Event("Woodchuck Stampede", _) => println("An unfortunate woodchuck stampede occured")
|
||||||
|
case _ => println("Nothing of importance occured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val evt = Event("Woodchuck Stampede", this)
|
||||||
|
val myListener = new MyListener()
|
||||||
|
myListener.listen(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Traits are polymorphic. Any type can be referred to by another type if related by extension or trait") {
|
||||||
|
|
||||||
|
case class Event(name: String, source: Any)
|
||||||
|
|
||||||
|
trait EventListener {
|
||||||
|
def listen(event: Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyListener extends EventListener {
|
||||||
|
def listen(event: Event) {
|
||||||
|
event match {
|
||||||
|
case Event("Moose Stampede", _) => println("An unfortunate moose stampede occured")
|
||||||
|
case _ => println("Nothing of importance occured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val evt = Event("Moose Stampede", this)
|
||||||
|
val myListener = new MyListener()
|
||||||
|
|
||||||
|
myListener.isInstanceOf[MyListener] should be(true)
|
||||||
|
myListener.isInstanceOf[EventListener] should be(true)
|
||||||
|
myListener.isInstanceOf[Any] should be(true)
|
||||||
|
myListener.isInstanceOf[AnyRef] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Traits can have concrete method implementations that can be mixed
|
||||||
|
| into concrete classes with it's own state""") {
|
||||||
|
|
||||||
|
trait ListLog {
|
||||||
|
var logCache = List[String]()
|
||||||
|
|
||||||
|
def log(value: String) {
|
||||||
|
logCache = logCache :+ value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Welder extends ListLog {
|
||||||
|
def weld() {
|
||||||
|
log("welding pipe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baker extends ListLog {
|
||||||
|
def bake() {
|
||||||
|
log("baking cake")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val welder = new Welder
|
||||||
|
welder.weld()
|
||||||
|
|
||||||
|
|
||||||
|
val baker = new Baker
|
||||||
|
baker.bake()
|
||||||
|
|
||||||
|
welder.logCache.size should be(1)
|
||||||
|
baker.logCache.size should be(1)
|
||||||
|
|
||||||
|
welder.logCache(0) should be("welding pipe")
|
||||||
|
baker.logCache(0) should be("baking cake")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Traits can have concrete implementations, but can still be overridden by classes that
|
||||||
|
| mixin the trait""") {
|
||||||
|
|
||||||
|
trait ListLog {
|
||||||
|
var logCache = List[String]()
|
||||||
|
|
||||||
|
def log(value: String) {
|
||||||
|
logCache = logCache :+ value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Welder extends Object with ListLog {
|
||||||
|
def weld() {
|
||||||
|
log("welding pipe")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def log(value: String) {
|
||||||
|
super.log("Weld Log : " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baker extends ListLog {
|
||||||
|
def bake() {
|
||||||
|
log("baking cake")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def log(value: String) {
|
||||||
|
super.log("Bake Log : " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val welder = new Welder
|
||||||
|
welder.weld() //Parenthesis are used to make calls on methods with side-effects
|
||||||
|
|
||||||
|
val baker = new Baker
|
||||||
|
baker.bake() //Parenthesis are used to make calls on methods with side-effects
|
||||||
|
|
||||||
|
welder.logCache.size should be(1)
|
||||||
|
baker.logCache.size should be(1)
|
||||||
|
|
||||||
|
welder.logCache(0) should be("Weld Log : welding pipe")
|
||||||
|
baker.logCache(0) should be("Bake Log : baking cake")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Traits can be stacked with other traits to create customizable decorative abstractions for a class""") {
|
||||||
|
|
||||||
|
trait Log {
|
||||||
|
//A log
|
||||||
|
def log(value: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TimedLog extends Log {
|
||||||
|
abstract override def log(value: String) {
|
||||||
|
super.log("January, 12, 2025 : " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait UserLog extends Log {
|
||||||
|
abstract override def log(value: String) {
|
||||||
|
super.log("Root said: " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ListLog extends Log {
|
||||||
|
var logCache = List[String]()
|
||||||
|
|
||||||
|
override def log(value: String) {
|
||||||
|
logCache = logCache :+ value
|
||||||
|
println(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Baker extends Object with ListLog with TimedLog with UserLog {
|
||||||
|
def bake() {
|
||||||
|
log("baking cake")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val baker = new Baker
|
||||||
|
baker.bake()
|
||||||
|
baker.logCache(0) should be("January, 12, 2025 : Root said: baking cake")
|
||||||
|
|
||||||
|
class Welder extends Object with ListLog with TimedLog {
|
||||||
|
// I don't want UserLog with a Welder
|
||||||
|
def weld() {
|
||||||
|
log("welding pipe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val welder = new Welder
|
||||||
|
welder.weld()
|
||||||
|
welder.logCache(0) should be("January, 12, 2025 : welding pipe")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Traits can be stacked with other traits to create customizable decorative
|
||||||
|
| abstractions for a class that weren't written in originally!""") {
|
||||||
|
|
||||||
|
trait Log {
|
||||||
|
//A log
|
||||||
|
def log(value: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TimedLog extends Log {
|
||||||
|
abstract override def log(value: String) {
|
||||||
|
super.log("January, 12, 2025 : " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait UserLog extends Log {
|
||||||
|
abstract override def log(value: String) {
|
||||||
|
super.log("Root said: " + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ListLog extends Log {
|
||||||
|
var logCache = List[String]()
|
||||||
|
|
||||||
|
override def log(value: String) {
|
||||||
|
logCache = logCache :+ value
|
||||||
|
println(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Baker() //define a class
|
||||||
|
val baker = new Baker with ListLog with UserLog with TimedLog //Pick and choose what traits to stack!
|
||||||
|
baker.log("baked cake")
|
||||||
|
baker.logCache(0) should be("Root said: January, 12, 2025 : baked cake") //Whoa!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("Traits are instantiated before a classes instantition") {
|
||||||
|
var sb = List[String]()
|
||||||
|
|
||||||
|
trait T1 {
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
val x = 1
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
class C1 extends T1 {
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
val y = 2
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = sb :+ ("Creating C1")
|
||||||
|
new C1
|
||||||
|
sb = sb :+ ("Created C1")
|
||||||
|
|
||||||
|
sb.mkString(";") should be("Creating C1;In T1: x=0;In T1: x=1;In C1: y=0;In C1: y=2;Created C1")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("Traits are instantiated before a classes instantition from left to right") {
|
||||||
|
var sb = List[String]()
|
||||||
|
|
||||||
|
trait T1 {
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
val x = 1
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
trait T2 {
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
val z = 1
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
}
|
||||||
|
|
||||||
|
class C1 extends T1 with T2 {
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
val y = 2
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = sb :+ ("Creating C1")
|
||||||
|
new C1
|
||||||
|
sb = sb :+ ("Created C1")
|
||||||
|
|
||||||
|
sb.mkString(";") should be(
|
||||||
|
"Creating C1;In T1: x=0;In T1: x=1;In T2: z=0;In T2: z=1;In C1: y=0;In C1: y=2;Created C1")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Instantiations are tracked and will not allow a duplicate instantiation.
|
||||||
|
| Note T1 extends T2, and C1 also extends T2, but T2 is only instantiated twice.""") {
|
||||||
|
|
||||||
|
var sb = List[String]()
|
||||||
|
|
||||||
|
trait T1 extends T2 {
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
val x = 1
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
trait T2 {
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
val z = 1
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
}
|
||||||
|
|
||||||
|
class C1 extends T1 with T2 {
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
val y = 2
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = sb :+ ("Creating C1")
|
||||||
|
new C1
|
||||||
|
sb = sb :+ ("Created C1")
|
||||||
|
|
||||||
|
sb.mkString(";") should
|
||||||
|
be("Creating C1;In T2: z=0;In T2: z=1;In T1: x=0;In T1: x=1;In C1: y=0;In C1: y=2;Created C1")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("The diamond of death (http://en.wikipedia.org/wiki/Diamond_problem) is avoided since " +
|
||||||
|
"instantiations are tracked and will not allow multiple instantiations") {
|
||||||
|
|
||||||
|
var sb = List[String]()
|
||||||
|
|
||||||
|
trait T1 {
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
val x = 1
|
||||||
|
sb = sb :+ ("In T1: x=%s".format(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
trait T2 extends T1 {
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
val z = 2
|
||||||
|
sb = sb :+ ("In T2: z=%s".format(z))
|
||||||
|
}
|
||||||
|
|
||||||
|
trait T3 extends T1 {
|
||||||
|
sb = sb :+ ("In T3: w=%s".format(w))
|
||||||
|
val w = 3
|
||||||
|
sb = sb :+ ("In T3: w=%s".format(w))
|
||||||
|
}
|
||||||
|
|
||||||
|
class C1 extends T2 with T3 {
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
val y = 4
|
||||||
|
sb = sb :+ ("In C1: y=%s".format(y))
|
||||||
|
}
|
||||||
|
|
||||||
|
sb = sb :+ ("Creating C1")
|
||||||
|
new C1
|
||||||
|
sb = sb :+ ("Created C1")
|
||||||
|
|
||||||
|
sb.mkString(";") should be
|
||||||
|
("Creating C1;In T1: x=0;In T1: x=1;In T2: z=0;In T2: z=2;" +
|
||||||
|
"In T3: w=0;In T3: w=3;In C1: y=0;In C1: y=4;Created C1")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
import Stream._
|
||||||
|
import collection.immutable.{TreeMap, TreeSet}
|
||||||
|
|
||||||
|
class AboutTraversables extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("""Traverables are the superclass of Lists, Arrays, Maps, Sets, Streams, and more.
|
||||||
|
| The methods involved can be applied to each other in a different type. ++ appends
|
||||||
|
| two Traversables together.""") {
|
||||||
|
|
||||||
|
val set = Set(1, 9, 10, 22)
|
||||||
|
val list = List(3, 4, 5, 10)
|
||||||
|
val result = set ++ list
|
||||||
|
result.size should be(7)
|
||||||
|
|
||||||
|
val result2 = list ++ set
|
||||||
|
result2.size should be(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""map will apply the given function on all elements of a
|
||||||
|
| Traversable and return a new collection of the result.""") {
|
||||||
|
val set = Set(1, 3, 4, 6)
|
||||||
|
val result = set.map(_ * 4)
|
||||||
|
result.last should be(24)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""flatten will smash all child Traversables within a Traversable""") {
|
||||||
|
val list = List(List(1), List(2, 3, 4), List(5, 6, 7), List(8, 9, 10))
|
||||||
|
list.flatten should be(List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""flatMap will not only apply the given function on all elements of a Traversable,
|
||||||
|
| but all elements within the elements and flatten the results""") {
|
||||||
|
val list = List(List(1), List(2, 3, 4), List(5, 6, 7), List(8, 9, 10))
|
||||||
|
val result = list.flatMap(_.map(_ * 4))
|
||||||
|
result should be(List(4, 8, 12, 16, 20, 24, 28, 32, 36, 40))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""flatMap of Options will filter out all Nones and Keep the Somes""") {
|
||||||
|
val list = List(1, 2, 3, 4, 5)
|
||||||
|
val result = list.flatMap(it => if (it % 2 == 0) Some(it) else None)
|
||||||
|
result should be(List(2, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""collect will apply a partial function to all elements of a Traversable
|
||||||
|
and will return a different collection. In this koan, a case fragment is a partial function.""") {
|
||||||
|
val list = List(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = list.collect {
|
||||||
|
case x: Int if (x % 2 == 0) => x * 3
|
||||||
|
}
|
||||||
|
result should be(List(12, 18, 24, 42))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""collect will apply a partial function to all elements of a Traversable
|
||||||
|
| and will return a different collection. In this koan, two case fragments are chained to create
|
||||||
|
| a more robust result.""") {
|
||||||
|
val list = List(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val partialFunction1: PartialFunction[Int, Int] = {
|
||||||
|
case x: Int if (x % 2 == 0) => x * 3
|
||||||
|
}
|
||||||
|
val partialFunction2: PartialFunction[Int, Int] = {
|
||||||
|
case y: Int if (y % 2 != 0) => y * 4
|
||||||
|
}
|
||||||
|
val result = list.collect(partialFunction1 orElse partialFunction2)
|
||||||
|
result should be(List(12, 18, 28, 24, 36, 52, 42))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""foreach will apply a function to all elements of a Traversable, but unlike
|
||||||
|
| the map function, it will not return anything since the return type is Unit, which
|
||||||
|
| is like a void return type in Java, C++""") {
|
||||||
|
val list = List(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
list.foreach(num => println(num * 4))
|
||||||
|
list should be(List(4, 6, 7, 8, 9, 13, 14))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toArray will convert any Traversable to an Array, which is a special wrapper around a
|
||||||
|
| primitive Java array.""") {
|
||||||
|
val set = Set(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = set.toArray
|
||||||
|
result.isInstanceOf[Array[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toList will convert any Traversable to a List.""") {
|
||||||
|
val set = Set(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = set.toList
|
||||||
|
result.isInstanceOf[List[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toList, as well as other conversion methods like toSet, toArray,
|
||||||
|
| will not convert if the collection type is the same.""") {
|
||||||
|
val list = List(5, 6, 7, 8, 9)
|
||||||
|
val result = list.toList
|
||||||
|
(result eq list) should be(true) //Reminder: eq tests for reference equality
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toIterable will convert any Traversable to an Iterable. This is a base
|
||||||
|
| trait for all Scala collections that define an iterator method to step
|
||||||
|
| through one-by-one the collection's elements.
|
||||||
|
| (see AboutIterable koan).""") {
|
||||||
|
|
||||||
|
val set = Set(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = set.toIterable
|
||||||
|
result.isInstanceOf[Iterable[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toSeq will convert any Traversable to a Seq which is an ordered Iterable
|
||||||
|
| and is the superclass to List, Queues, and Vectors. Sequences provide
|
||||||
|
| a method apply for indexing. Indices range from 0 up the the
|
||||||
|
| length of a sequence.""") {
|
||||||
|
val set = Set(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = set.toSeq
|
||||||
|
result.isInstanceOf[Seq[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toIndexedSeq will convert any Traversable to an IndexedSeq which is
|
||||||
|
| an indexed sequence used in
|
||||||
|
| Vectors and Strings""") {
|
||||||
|
val set = Set(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = set.toIndexedSeq
|
||||||
|
result.isInstanceOf[IndexedSeq[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toStream will convert any Traversable to an Stream which is
|
||||||
|
| which is a lazy lists where elements are evaluated as they
|
||||||
|
| are needed.""") {
|
||||||
|
val list = List(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = list.toStream
|
||||||
|
result.isInstanceOf[Stream[Int]] should be(true)
|
||||||
|
(result take 3) should be(List(4, 6, 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toSet will convert any Traversable to an Set which is
|
||||||
|
| which is a collection of unordered, unique values""") {
|
||||||
|
val list = List(4, 6, 7, 8, 9, 13, 14)
|
||||||
|
val result = list.toSet
|
||||||
|
result.isInstanceOf[Set[Int]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toMap will convert any Traversable to a Map. How it's
|
||||||
|
| used, depends on the original collection, if it's a List or Seq,
|
||||||
|
| it should be of parameterized type Tuple2.""") {
|
||||||
|
val list = List(("Phoenix" -> "Arizona"), ("Austin" -> "Texas"))
|
||||||
|
val result = list.toMap
|
||||||
|
result.isInstanceOf[Map[String, String]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""toMap will convert a Set to a Map,
|
||||||
|
| it should be of parameterized type Tuple2.""") {
|
||||||
|
val set = Set(("Phoenix" -> "Arizona"), ("Austin" -> "Texas"))
|
||||||
|
val result = set.toMap
|
||||||
|
result.isInstanceOf[Map[String, String]] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""isEmpty is pretty self evident""") {
|
||||||
|
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
|
||||||
|
map.isEmpty should be(false)
|
||||||
|
|
||||||
|
val set = Set()
|
||||||
|
set.isEmpty should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""nonEmpty is pretty self evident too""") {
|
||||||
|
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
|
||||||
|
map.nonEmpty should be(true)
|
||||||
|
|
||||||
|
val set = Set()
|
||||||
|
set.nonEmpty should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""size provides the size of the traversable""") {
|
||||||
|
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
|
||||||
|
map.size should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""hasDefiniteSize will return true if there is traversable that has a
|
||||||
|
finite end, otherwise false""") {
|
||||||
|
val map = Map("Phoenix" -> "Arizona", "Austin" -> "Texas")
|
||||||
|
map.hasDefiniteSize should be(true)
|
||||||
|
|
||||||
|
import Stream.cons
|
||||||
|
val stream = cons(0, cons(1, Stream.empty))
|
||||||
|
stream.hasDefiniteSize should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""head will return the first element of an ordered collection, or some random
|
||||||
|
| element if order is not defined like in a Set or Map""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.head should be(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""headOption will return the first element as an Option of an order collection,
|
||||||
|
| or some random element if order is not defined. If a first element
|
||||||
|
| is not available, then None is returned""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.headOption should be(Some(10))
|
||||||
|
|
||||||
|
val list2 = List()
|
||||||
|
list2.headOption should be(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""last will return the last element of an ordered collection, or some random
|
||||||
|
| element if order is not defined like in a Set or Map""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.last should be(22)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""lastOption will return the first element as an Option of an order collection,
|
||||||
|
| or some random element if order is not defined. If a first element
|
||||||
|
| is not available, then None is returned""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.lastOption should be(Some(22))
|
||||||
|
|
||||||
|
val list2 = List()
|
||||||
|
list2.lastOption should be(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""find will locate an the first item that matches a predicate p as Some or None if
|
||||||
|
| an element is not found""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.find(_ % 2 != 0) should be(Some(19))
|
||||||
|
|
||||||
|
val list2 = List(4, 8, 16)
|
||||||
|
list2.find(_ % 2 != 0) should be(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""tail will return the rest of the collection without the head""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.tail should be(List(19, 45, 1, 22))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""init will return the rest of the collection without the last""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.init should be(List(10, 19, 45, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Given a `from` index, and a `to` index, slice will return the part of the
|
||||||
|
| collection including `from`, and excluding `to`""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.slice(1, 3) should be(List(19, 45))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Take will return the the first number of elements given.""") {
|
||||||
|
val list = List(10, 19, 45, 1, 22)
|
||||||
|
list.take(3) should be(List(10, 19, 45))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Take is used often with Streams, and Streams after all are Traversable""") {
|
||||||
|
def streamer(v: Int): Stream[Int] = cons(v, streamer(v + 1))
|
||||||
|
val a = streamer(2)
|
||||||
|
(a take 3 toList) should be(List(2, 3, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Drop will take the rest of the Traversable except
|
||||||
|
| the number of elements given""") {
|
||||||
|
def streamer(v: Int): Stream[Int] = cons(v, streamer(v + 1))
|
||||||
|
val a = streamer(2)
|
||||||
|
((a drop 6) take 3).toList should be(List(8, 9, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""takeWhile will continually accumulate elements until a predicate
|
||||||
|
| is no longer satisfied. In this koan, TreeSet is Traversable.
|
||||||
|
| TreeSet also is also sorted.""") {
|
||||||
|
val treeSet = TreeSet(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
treeSet.takeWhile(_ < 100) should be(TreeSet(4, 5, 10, 39, 44, 87))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""dropWhile will continually drop elements until a predicate
|
||||||
|
| is no longer satisfied. Again, TreeSet is Traversable.
|
||||||
|
| TreeSet also is also sorted.""") {
|
||||||
|
val treeSet = TreeSet(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
treeSet.dropWhile(_ < 100) should be(TreeSet(100, 200))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""filter will take out all elements that don't satisfy a predicate. An
|
||||||
|
| Array is also Traversable.""") {
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
array.filter(_ < 100) should be(Array(87, 44, 5, 4, 10, 39))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""filterNot will take out all elements that satisfy a predicate. An
|
||||||
|
| Array is also Traversable.""") {
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
array.filterNot(_ < 100) should be(Array(200, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""splitAt will split a Traversable at a position, returning a 2 product
|
||||||
|
| Tuple. Array is Traversable. splitAt is also defined as
|
||||||
|
| (xs take n, xs drop n)""") {
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = array splitAt 3
|
||||||
|
result._1 should be(Array(87, 44, 5))
|
||||||
|
result._2 should be(Array(4, 200, 10, 39, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""span will split a Traversable according to predicate, returning
|
||||||
|
| a 2 product Tuple. Array is Traversable, span
|
||||||
|
| is also defined as (xs takeWhile p, xs dropWhile p)""") {
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = array span (_ < 100)
|
||||||
|
result._1 should be(Array(87, 44, 5, 4))
|
||||||
|
result._2 should be(Array(200, 10, 39, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""partition will split a Traversable according to predicate, return
|
||||||
|
| a 2 product Tuple. The left side are the elements satisfied by
|
||||||
|
| the predicate, the right side is not. Array is Traversable,
|
||||||
|
| partition is also defined as (xs filter p, xs filterNot p)""") {
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = array partition (_ < 100)
|
||||||
|
result._1 should be(Array(87, 44, 5, 4, 10, 39))
|
||||||
|
result._2 should be(Array(200, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""groupBy will categorize a Traversable according to function, and return
|
||||||
|
a map with the results. This koan uses Partial Function chaining. If you are
|
||||||
|
still unfamiliar with PartialFunctions, see AboutPartialFunctions koans.""") {
|
||||||
|
|
||||||
|
val array = Array(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
|
||||||
|
val oddAndSmallPartial: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if (x % 2 != 0 && x < 100) => "Odd and less than 100"
|
||||||
|
}
|
||||||
|
|
||||||
|
val evenAndSmallPartial: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if (x != 0 && x % 2 == 0 && x < 100) => "Even and less than 100"
|
||||||
|
}
|
||||||
|
|
||||||
|
val negativePartial: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if (x < 0) => "Negative Number"
|
||||||
|
}
|
||||||
|
|
||||||
|
val largePartial: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if (x > 99) => "Large Number"
|
||||||
|
}
|
||||||
|
|
||||||
|
val zeroPartial: PartialFunction[Int, String] = {
|
||||||
|
case x: Int if (x == 0) => "Zero"
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = array groupBy {
|
||||||
|
oddAndSmallPartial orElse
|
||||||
|
evenAndSmallPartial orElse
|
||||||
|
negativePartial orElse
|
||||||
|
largePartial orElse
|
||||||
|
zeroPartial
|
||||||
|
}
|
||||||
|
|
||||||
|
(result("Even and less than 100") size) should be(3)
|
||||||
|
(result("Large Number") size) should be(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""forall will determine if a predicate is valid for all members of a
|
||||||
|
| Traversable.""") {
|
||||||
|
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = list forall (_ < 100)
|
||||||
|
result should be(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`exists` should've been called `forsome`. `exists` will determine if a predicate
|
||||||
|
| is valid for some members of a Traversable.""") {
|
||||||
|
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = list exists (_ < 100)
|
||||||
|
result should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`count` will count the number of elements that satisfy a predicate
|
||||||
|
| in a Traversable.""") {
|
||||||
|
val list = List(87, 44, 5, 4, 200, 10, 39, 100)
|
||||||
|
val result = list count (_ < 100)
|
||||||
|
result should be(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan(""" `/:` or `foldLeft` will combine an operation starting with a seed and combining from the left. Fold Left
|
||||||
|
| is defined as (seed /: list), where seed is the initial value. Once the fold is established, you
|
||||||
|
| provide a function that takes two arguments. The first argument is the running total of the operation,
|
||||||
|
| and the second element is the next element of the list.
|
||||||
|
|
|
||||||
|
| Given a Traversable (x1, x2, x3, x4), an initial value of init, an operation op,
|
||||||
|
| foldLeft is defined as: (((init op x1) op x2) op x3) op x4)""") {
|
||||||
|
val list = List(5, 4, 3, 2, 1)
|
||||||
|
val result = (0 /: list) {
|
||||||
|
(`running total`, `next element`) => `running total` - `next element`
|
||||||
|
}
|
||||||
|
result should be(-15)
|
||||||
|
|
||||||
|
val result2 = list.foldLeft(0) {
|
||||||
|
(`running total`, `next element`) => `running total` - `next element`
|
||||||
|
}
|
||||||
|
result2 should be(-15)
|
||||||
|
|
||||||
|
val result3 = (0 /: list)(_ - _) //Short hand
|
||||||
|
result3 should be(-15)
|
||||||
|
|
||||||
|
val result4 = list.foldLeft(0)(_ - _)
|
||||||
|
result4 should be(-15)
|
||||||
|
|
||||||
|
(((((0 - 5) - 4) - 3) - 2) - 1) should be(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan(""" `:\` or foldRight` will combine an operation starting with a seed and combining from the right. Fold right
|
||||||
|
| is defined as (list :\ seed), where seed is the initial value. Once the fold is established, you
|
||||||
|
| provide a function that takes two elements. The first is the next element of the list, and the
|
||||||
|
| second element is the running total of the operation.
|
||||||
|
|
|
||||||
|
| Given a Traversable (x1, x2, x3, x4), an initial value of init, an operation op,
|
||||||
|
| foldRight is defined as: x1 op (x2 op (x3 op (x4 op init)))""") {
|
||||||
|
|
||||||
|
val list = List(5, 4, 3, 2, 1)
|
||||||
|
val result = (list :\ 0) {
|
||||||
|
(`next element`, `running total`) => `next element` - `running total`
|
||||||
|
}
|
||||||
|
result should be(3)
|
||||||
|
|
||||||
|
val result2 = (list :\ 0) {
|
||||||
|
(`next element`, `running total`) => `next element` - `running total`
|
||||||
|
}
|
||||||
|
result2 should be(3)
|
||||||
|
|
||||||
|
val result3 = (list :\ 0)(_ - _) //Short hand
|
||||||
|
result3 should be(3)
|
||||||
|
|
||||||
|
val result4 = list.foldRight(0)(_ - _)
|
||||||
|
result4 should be(3)
|
||||||
|
|
||||||
|
(5 - (4 - (3 - (2 - (1 - 0))))) should be(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`reduceLeft` is the similar to foldLeft, except that the seed is the head value""") {
|
||||||
|
val intList = List(5, 4, 3, 2, 1)
|
||||||
|
intList.reduceLeft {
|
||||||
|
_ + _
|
||||||
|
} should be(15)
|
||||||
|
|
||||||
|
val stringList = List("Do", "Re", "Me", "Fa", "So", "La", "Te", "Do")
|
||||||
|
stringList.reduceLeft {
|
||||||
|
_ + _
|
||||||
|
} should be("DoReMeFaSoLaTeDo")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`reduceRight` is the similar to foldRight, except that the seed is the last value""") {
|
||||||
|
val intList = List(5, 4, 3, 2, 1)
|
||||||
|
intList.reduceRight {
|
||||||
|
_ + _
|
||||||
|
} should be(15)
|
||||||
|
|
||||||
|
val stringList = List("Do", "Re", "Me", "Fa", "So", "La", "Te", "Do")
|
||||||
|
stringList.reduceRight {
|
||||||
|
_ + _
|
||||||
|
} should be("DoReMeFaSoLaTeDo")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""There are some methods that take much of the folding work out by providing basic functionality.
|
||||||
|
| `sum` will add all the elements, product will multiply, min would determine the smallest element, and
|
||||||
|
| `max` the largest.""") {
|
||||||
|
val intList = List(5, 4, 3, 2, 1)
|
||||||
|
intList.sum should be(15)
|
||||||
|
intList.product should be(120)
|
||||||
|
intList.max should be(5)
|
||||||
|
intList.min should be(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""You would choose foldLeft/reduceLeft or foldRight/reduceRight based on your mathematical goal.
|
||||||
|
| One other reason for deciding is performance. foldLeft is more perfomant since it uses
|
||||||
|
| tail recursion and is optimized. This koan will either work or you will receieve a
|
||||||
|
| StackOverflowError. If you do receive a StackOverflowError, try reducing the MAX_SIZE value.""") {
|
||||||
|
|
||||||
|
val MAX_SIZE = 1000000
|
||||||
|
val reduceLeftStartTime = new java.util.Date
|
||||||
|
((1 to MAX_SIZE) reduceLeft (_ + _))
|
||||||
|
val reduceLeftEndTime = new java.util.Date
|
||||||
|
|
||||||
|
|
||||||
|
val reduceRightStartTime = new java.util.Date
|
||||||
|
((1 to MAX_SIZE) reduceRight (_ + _))
|
||||||
|
val reduceRightEndTime = new java.util.Date
|
||||||
|
|
||||||
|
val totalReduceLeftTime = (reduceLeftEndTime.getTime - reduceLeftStartTime.getTime)
|
||||||
|
val totalReduceRightTime = (reduceRightEndTime.getTime - reduceRightStartTime.getTime)
|
||||||
|
|
||||||
|
(totalReduceRightTime > totalReduceLeftTime) should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`transpose` will take a traversable of traversables and group them by their position in
|
||||||
|
| it's own traversable. E.g. ((x1, x2),(y1, y2)).transpose = (x1, y1), (x2, y2).
|
||||||
|
| or ((x1, x2),(y1, y2),(z1, z2)).transpose = ((x1, y1, z1), (x2, y2, z2), (x3, y3, z3))""") {
|
||||||
|
val list = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
|
||||||
|
list.transpose should be(List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9)))
|
||||||
|
|
||||||
|
val list2 = List(List(1), List(4))
|
||||||
|
list2.transpose should be(List(List(1, 4)))
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`mkString` will format a Traversable using a given string as the delimiter.""") {
|
||||||
|
val list = List(1, 2, 3, 4, 5)
|
||||||
|
list.mkString(",") should be("1,2,3,4,5")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`mkString` will also take a beginning and ending string to surround the list.""") {
|
||||||
|
val list = List(1, 2, 3, 4, 5)
|
||||||
|
list.mkString(">", ",", "<") should be(">1,2,3,4,5<")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""`addString` will take a StringBuilder to add the contents of list into the builder.""") {
|
||||||
|
val stringBuilder = new StringBuilder()
|
||||||
|
val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
|
||||||
|
stringBuilder.append("I want all numbers 6-12: ")
|
||||||
|
list.filter(it => it > 5 && it < 13).addString(stringBuilder, ",")
|
||||||
|
stringBuilder.mkString should be("I want all numbers 6-12: 6,7,8,9,10,11,12")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Traversables can have views which allow you to efficiently do compound work.") {
|
||||||
|
val lst = List(1, 2, 3)
|
||||||
|
var history = List[String]()
|
||||||
|
|
||||||
|
def addHistory(s: String) {
|
||||||
|
history = history :+ s
|
||||||
|
}
|
||||||
|
|
||||||
|
lst.map {x => addHistory("Doubling %s".format(x)); x * 2}
|
||||||
|
.map {x => addHistory("Adding 1 to %s".format(x)); x + 1}
|
||||||
|
|
||||||
|
history(0) should be("Doubling 1")
|
||||||
|
history(1) should be("Doubling 2")
|
||||||
|
history(2) should be("Doubling 3")
|
||||||
|
history(3) should be("Adding 1 to 2")
|
||||||
|
history(4) should be("Adding 1 to 4")
|
||||||
|
history(5) should be("Adding 1 to 6")
|
||||||
|
|
||||||
|
history = List[String]()
|
||||||
|
|
||||||
|
lst.view.map {x => addHistory("Doubling %s".format(x)); x * 2}
|
||||||
|
.map {x => addHistory("Adding 1 to %s".format(x)); x + 1}.force
|
||||||
|
|
||||||
|
history(0) should be("Doubling 1")
|
||||||
|
history(1) should be("Adding 1 to 2")
|
||||||
|
history(2) should be("Doubling 2")
|
||||||
|
history(3) should be("Adding 1 to 4")
|
||||||
|
history(4) should be("Doubling 3")
|
||||||
|
history(5) should be("Adding 1 to 6")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Views can also accept a `to` and `from` value which takes the substring and performs your view
|
||||||
|
| functions on the subset.""") {
|
||||||
|
val list = List(1,2,3,4,5,6,7,8)
|
||||||
|
list.view(3,6).map(_+2).map(_*10).force should be (List(60,70,80))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class AboutTuples extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
koan("Tuples can be created easily") {
|
||||||
|
val tuple = ("apple", "dog")
|
||||||
|
tuple should be("apple", "dog")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Tuple items may be accessed individually") {
|
||||||
|
val tuple = ("apple", "dog")
|
||||||
|
val fruit = tuple._1
|
||||||
|
val animal = tuple._2
|
||||||
|
|
||||||
|
fruit should be("apple")
|
||||||
|
animal should be("dog")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Tuples may be of mixed type") {
|
||||||
|
val tuple5 = ("a", 1, 2.2, new Date(), BigDecimal(5))
|
||||||
|
|
||||||
|
tuple5._1.isInstanceOf[String]
|
||||||
|
tuple5._2.isInstanceOf[Int]
|
||||||
|
tuple5._3 should be(2.2)
|
||||||
|
tuple5._4.isInstanceOf[Date]
|
||||||
|
tuple5._5 should be(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutTypeAliases extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("A type alias merely allows you to call a class by a different name") {
|
||||||
|
case class Student(firstName: String, lastName: String)
|
||||||
|
|
||||||
|
type Pupil = Student
|
||||||
|
val harryPotter = new Pupil("Harry", "Potter")
|
||||||
|
harryPotter.firstName should be("Harry")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Although you can't make an type alias of an singleton object,
|
||||||
|
| you can just assign a singleton object to a var or val to
|
||||||
|
| create the alias.
|
||||||
|
|
|
||||||
|
| When you assign a val or var to a singleton object,
|
||||||
|
| the type is <object-name>.type.""") {
|
||||||
|
|
||||||
|
object StarGaze {
|
||||||
|
def lookOntoTheSky = "I see Scorpio to the south"
|
||||||
|
}
|
||||||
|
|
||||||
|
val Observatory = StarGaze
|
||||||
|
val LoversLookout: Observatory.type = Observatory
|
||||||
|
|
||||||
|
Observatory.lookOntoTheSky should be("I see Scorpio to the south")
|
||||||
|
LoversLookout.lookOntoTheSky should be("I see Scorpio to the south")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""You can use <object-name>.type as a method parameter to accept singleton objects.""") {
|
||||||
|
object StarGaze {
|
||||||
|
def lookOntoTheSky = "I see Scorpio to the south"
|
||||||
|
}
|
||||||
|
|
||||||
|
def lookout(starGazer:StarGaze.type) = {
|
||||||
|
starGazer.lookOntoTheSky
|
||||||
|
}
|
||||||
|
lookout(StarGaze) should be ("I see Scorpio to the south")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutTypeBounds extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class Fruit
|
||||||
|
|
||||||
|
abstract class Citrus extends Fruit
|
||||||
|
|
||||||
|
class Orange extends Citrus
|
||||||
|
|
||||||
|
class Tangelo extends Citrus
|
||||||
|
|
||||||
|
class Apple extends Fruit
|
||||||
|
|
||||||
|
class Banana extends Fruit
|
||||||
|
|
||||||
|
koan("""You can declare an upper bound of type using the <: operator. The <: operator designates that the
|
||||||
|
| operator on the left must be a subtype of the type on the right.""") {
|
||||||
|
|
||||||
|
class MyContainer[A <: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
|
||||||
|
citrusBasket.set(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
citrusBasket.set(new Tangelo)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Variance notations are for assigment while bounds are rules on containment.
|
||||||
|
| Although variance and bounds are different, they can be used
|
||||||
|
| with one another. In this koan we can put in all anything that is a Citrus or any of
|
||||||
|
| subclasses and we can assign it to variables
|
||||||
|
| with a type of Citrus or any of its superclasses.""") {
|
||||||
|
|
||||||
|
class MyContainer[+A <: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange) //+A allows the assignment
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer[Citrus](new Tangelo) //+A allows the assignment
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3: MyContainer[Citrus] = new MyContainer[Tangelo](new Tangelo) //+A allows the assignment
|
||||||
|
citrusBasket3.contents should be("Tangelo")
|
||||||
|
|
||||||
|
val citrusBasket4: MyContainer[Tangelo] = new MyContainer[Tangelo](new Tangelo) //+A allows the assignment
|
||||||
|
citrusBasket4.contents should be("Tangelo")
|
||||||
|
|
||||||
|
val citrusBasket5: MyContainer[Citrus] = citrusBasket4
|
||||||
|
citrusBasket5.contents should be("Tangelo")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Variance notations are for assigment while bounds are rules on containment. Although variance and bounds
|
||||||
|
| are different, they can be used
|
||||||
|
| with one another. This koan will allow all citruses to be placed in the container, and will allow
|
||||||
|
| the container to be assigned to
|
||||||
|
| variables of a type less than Citrus.""") {
|
||||||
|
|
||||||
|
class MyContainer[+A <: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket = new MyContainer[Citrus](new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
val citrusBasket2 = new MyContainer[Orange](new Orange)
|
||||||
|
citrusBasket2.contents should be("Orange")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""This koan uses the contravariant type upper bound by Citrus, therefore any object can be created that is a Citrus or any
|
||||||
|
| of its subtypes.""") {
|
||||||
|
|
||||||
|
class MyContainer[-A <: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Tangelo")
|
||||||
|
|
||||||
|
val citrusBasket4: MyContainer[Tangelo] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Tangelo")
|
||||||
|
|
||||||
|
val citrusBasket5 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket5.contents should be("Tangelo")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Lower bounds; invariant""") {
|
||||||
|
|
||||||
|
class MyContainer[A >: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket4 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Lower bounds contravariant""") {
|
||||||
|
class MyContainer[-A >: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket4 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Lower bounds covariant""") {
|
||||||
|
class MyContainer[+A >: Citrus](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus") //Why? very important!
|
||||||
|
|
||||||
|
val citrusBasket5 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket5.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Both upper and lower bounds; invariant""") {
|
||||||
|
class MyContainer[A >: Citrus <: AnyRef](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket4 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Both upper and lower bounds; contravariant""") {
|
||||||
|
class MyContainer[-A >: Citrus <: AnyRef](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket4 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""Both upper and lower bounds; covariant""") {
|
||||||
|
class MyContainer[+A >: Citrus <: AnyRef](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket2: MyContainer[Citrus] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket2.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket3 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket3.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket4 = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket4.contents should be("Citrus")
|
||||||
|
|
||||||
|
val citrusBasket5: MyContainer[Fruit] = new MyContainer(new Tangelo)
|
||||||
|
citrusBasket5.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Do I need koans for overriding and subclassing?
|
||||||
|
//TODO: Check if subclasses of parents who implement traits still get traits
|
||||||
|
//TODO: koan("() => Unit is a type, and so is => Unit, and so is Int, Int => Int")
|
||||||
|
//TODO: Do we have anything for :_* to fit it into an Array, there was some trick I am forgetting.
|
||||||
|
|
||||||
|
|
||||||
|
//(02:26:52 PM) retronym: M[A] <: M[B] if A <: B Covariance
|
||||||
|
//(02:27:12 PM) retronym: M[A] <: M[B] if B <: A Contravariance
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutTypeProjections extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
|
||||||
|
class Fruit
|
||||||
|
|
||||||
|
class Citrus extends Fruit
|
||||||
|
|
||||||
|
class Orange extends Citrus
|
||||||
|
|
||||||
|
class Tangelo extends Citrus
|
||||||
|
|
||||||
|
class Apple extends Fruit
|
||||||
|
|
||||||
|
class Banana extends Fruit
|
||||||
|
|
||||||
|
koan("In generic form3") {
|
||||||
|
trait X {
|
||||||
|
type Y <: Fruit
|
||||||
|
}
|
||||||
|
|
||||||
|
class TangeloBasket extends X {
|
||||||
|
type Y = Tangelo
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppleBasket extends X {
|
||||||
|
type Y = Apple
|
||||||
|
}
|
||||||
|
classOf[TangeloBasket#Y].getSimpleName should be("Tangelo")
|
||||||
|
classOf[AppleBasket#Y].getSimpleName should be("Apple")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("In generic form4") {
|
||||||
|
trait X {
|
||||||
|
type Y <: Fruit
|
||||||
|
|
||||||
|
def whatsFeedingMe: String
|
||||||
|
}
|
||||||
|
|
||||||
|
val x = new X {
|
||||||
|
type Y = Tangelo
|
||||||
|
|
||||||
|
override def whatsFeedingMe = classOf[Y].getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val z = new X {
|
||||||
|
type Y = Orange
|
||||||
|
|
||||||
|
override def whatsFeedingMe = classOf[Y].getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
println(x.whatsFeedingMe)
|
||||||
|
println(z.whatsFeedingMe)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("In generic form65") {
|
||||||
|
trait X {
|
||||||
|
type Y
|
||||||
|
}
|
||||||
|
|
||||||
|
val x = new X {
|
||||||
|
type Y = Tangelo
|
||||||
|
}
|
||||||
|
|
||||||
|
val z = new X {
|
||||||
|
type Y = Orange
|
||||||
|
}
|
||||||
|
|
||||||
|
class AwesomeX extends X {
|
||||||
|
type Y = String
|
||||||
|
}
|
||||||
|
|
||||||
|
println(classOf[AwesomeX#Y].getSimpleName)
|
||||||
|
|
||||||
|
val r = new X {
|
||||||
|
type Y = Apple
|
||||||
|
}
|
||||||
|
|
||||||
|
println(classOf[r.Y].getSimpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
scala> val r = new X {
|
||||||
|
| type Y = String
|
||||||
|
| }
|
||||||
|
<console>:9: error: overriding type Y in trait X, which equals String;
|
||||||
|
type Y needs `override' modifier
|
||||||
|
type Y = String
|
||||||
|
^
|
||||||
|
|
||||||
|
scala> val r = new X {
|
||||||
|
| override type Y = Int
|
||||||
|
| }
|
||||||
|
<console>:9: error: overriding type Y in trait X, which equals String;
|
||||||
|
type Y has incompatible type
|
||||||
|
override type Y = Int
|
||||||
|
^
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutTypeSignatures extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("In Java you declare a generic type within a <>, in Scala it is []") {
|
||||||
|
val z: List[String] = "Do" :: "Re" :: "Mi" :: "Fa" :: "So" :: "La" :: "Te" :: "Do" :: Nil
|
||||||
|
z(3) should be("Fa")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Most of the time, Scala will infer the type and [] are optional") {
|
||||||
|
val z = "Do" :: "Re" :: "Mi" :: "Fa" :: "So" :: "La" :: "Te" :: "Do" :: Nil //Infers that the list assigned to variable is of type List[String]
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("A trait can be declared containing a type, where a concrete implmenter will satisfy the type") {
|
||||||
|
trait Randomizer[A] {
|
||||||
|
def draw: A
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntRandomizer extends Randomizer[Int] {
|
||||||
|
def draw = {
|
||||||
|
import util.Random
|
||||||
|
Random.nextInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intRand = new IntRandomizer
|
||||||
|
intRand.draw should be < Int.MaxValue
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Class meta-information can be retrieved by class name by using classOf[className]") {
|
||||||
|
classOf[String].getCanonicalName should be("java.lang.String")
|
||||||
|
classOf[String].getSimpleName should be("String")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Class meta-information can be derived from an object reference using getClass()") {
|
||||||
|
val zoom = "zoom"
|
||||||
|
zoom.getClass should be(classOf[String])
|
||||||
|
zoom.getClass.getCanonicalName should be("java.lang.String")
|
||||||
|
zoom.getClass.getSimpleName should be("String")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("isInstanceOf[className] is used to determine the if an object reference is an instance of given class") {
|
||||||
|
trait Randomizer[A] {
|
||||||
|
def draw: A
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntRandomizer extends Randomizer[Int] {
|
||||||
|
override def draw = {
|
||||||
|
import util.Random
|
||||||
|
Random.nextInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intRand = new IntRandomizer
|
||||||
|
intRand.draw.isInstanceOf[Int] should be(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("asInstanceOf[className] is used to cast one reference to another") {
|
||||||
|
trait Randomizer[A] {
|
||||||
|
def draw: A
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntRandomizer extends Randomizer[Int] {
|
||||||
|
override def draw = {
|
||||||
|
import util.Random
|
||||||
|
Random.nextInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intRand = new IntRandomizer
|
||||||
|
val rand = intRand
|
||||||
|
val intRand2 = rand.asInstanceOf[IntRandomizer]
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("""asInstanceOf[className] will throw a ClassCastException if a class derived from
|
||||||
|
| and the class target aren't from the same inheritance branch""") {
|
||||||
|
trait Randomizer[A] {
|
||||||
|
def draw: A
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntRandomizer extends Randomizer[Int] {
|
||||||
|
def draw = {
|
||||||
|
import util.Random
|
||||||
|
Random.nextInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val intRand = new IntRandomizer
|
||||||
|
|
||||||
|
intercept[ClassCastException] {
|
||||||
|
intRand.asInstanceOf[String] //intRand cannot be cast to String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("null.asInstanceOf[className] can be used to generate basic default values") {
|
||||||
|
null.asInstanceOf[String] should be(null)
|
||||||
|
null.asInstanceOf[Int] should be(0)
|
||||||
|
null.asInstanceOf[Short] should be(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* TODO: This probably needs to move to another category,
|
||||||
|
TODO: since this class is supposed to be about type signatures */
|
||||||
|
koan("""Classes can be abstract. Abstract classes can define some methods
|
||||||
|
| concretely or may rely on it\'s subclasses to implement.
|
||||||
|
| If a method has no body and is in
|
||||||
|
| an abstract class, the method is considered abstract.""") {
|
||||||
|
abstract class Parent {
|
||||||
|
def add(x: Int): Int
|
||||||
|
|
||||||
|
//this is considered abstract
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
def add(x: Int): Int = x + 3
|
||||||
|
}
|
||||||
|
|
||||||
|
new Child().add(3) should be(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: This probably needs to move to another category,
|
||||||
|
TODO: since this class is supposed to be about type signatures */
|
||||||
|
koan("""Same koan as above. Except that concrete methods
|
||||||
|
| can have the modifier override to designate that it overrides a parent class.""") {
|
||||||
|
abstract class Parent {
|
||||||
|
def add(x: Int): Int
|
||||||
|
|
||||||
|
//this is considered abstract
|
||||||
|
}
|
||||||
|
|
||||||
|
class Child extends Parent {
|
||||||
|
override def add(x: Int): Int = x + 3
|
||||||
|
|
||||||
|
//explicitly
|
||||||
|
}
|
||||||
|
|
||||||
|
new Child().add(3) should be(6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
|
||||||
|
class AboutTypeVariance extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class Fruit
|
||||||
|
|
||||||
|
abstract class Citrus extends Fruit
|
||||||
|
|
||||||
|
class Orange extends Citrus
|
||||||
|
|
||||||
|
class Tangelo extends Citrus
|
||||||
|
|
||||||
|
class Apple extends Fruit
|
||||||
|
|
||||||
|
class Banana extends Fruit
|
||||||
|
|
||||||
|
koan("""Using type inference the type that you instantiate it will be the val or var reference type""") {
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val fruitBasket = new MyContainer(new Orange())
|
||||||
|
fruitBasket.contents should be("Orange")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""You can explicitly declare the type variable of the object during instantiation""") {
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val fruitBasket = new MyContainer[Fruit](new Orange())
|
||||||
|
fruitBasket.contents should be("Fruit")
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("You can coerece your object to a type.") {
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val fruitBasket: MyContainer[Fruit] = new MyContainer(new Orange())
|
||||||
|
fruitBasket.contents should be("Fruit")
|
||||||
|
}
|
||||||
|
|
||||||
|
// That one probably blew your mind. Now if you assign a type to the instantiation,
|
||||||
|
// that's different to the variable type, you'll have problems. You may want to take time after this
|
||||||
|
// o compare this koan with the previous koan to compare and contrast. """) {
|
||||||
|
|
||||||
|
|
||||||
|
koan("variable type must match assigned type") {
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncomment the following line
|
||||||
|
// val fruitBasket:MyContainer[Fruit] = new MyContainer[Orange](new Orange())
|
||||||
|
}
|
||||||
|
|
||||||
|
// So, if you want to set a Fruit basket to an orange basket so how do we fix that? You make it covariant using +.
|
||||||
|
// This will allow you to set the your container to a either a variable with the same type or parent type.
|
||||||
|
// In other words, you can assign MyContainer[Fruit] or MyContainer[Citrus]."""
|
||||||
|
koan("covariance lets you specify the container of that type or parent type") {
|
||||||
|
|
||||||
|
class MyContainer[+A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val fruitBasket: MyContainer[Fruit] = new MyContainer[Orange](new Orange())
|
||||||
|
fruitBasket.contents should be("Orange")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The problem with covariance is that you can't mutate, set, or change the object since
|
||||||
|
// it has to guarantee that what you put in has to be that type. In other words the reference is a fruit basket,
|
||||||
|
// but we still have to make sure that no other fruit can be placed in our orange basket"""
|
||||||
|
|
||||||
|
koan("mutating an object is not allowed with covariance") {
|
||||||
|
|
||||||
|
class MyContainer[+A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] val item = a
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val fruitBasket: MyContainer[Fruit] = new MyContainer[Orange](new Orange())
|
||||||
|
fruitBasket.contents should be("Orange")
|
||||||
|
|
||||||
|
class NavelOrange extends Orange //Creating a subtype to prove a point
|
||||||
|
// val navelOrangeBasket: MyContainer[NavelOrange] = new MyContainer[Orange](new Orange()) //Bad!
|
||||||
|
// val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Orange](new Orange()) //Bad!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declaring - indicates contravariance variance.
|
||||||
|
// Using - you can apply any container with a certain type to a container with a superclass of that type.
|
||||||
|
// This is reverse to covariant. In our example, we can set a citrus basket to
|
||||||
|
// an orange or tangelo basket. Since an orange or tangelo basket is a citrus basket
|
||||||
|
|
||||||
|
koan("contravariance is the opposite of covariance") {
|
||||||
|
|
||||||
|
class MyContainer[-A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
val orangeBasket: MyContainer[Orange] = new MyContainer[Citrus](new Tangelo)
|
||||||
|
orangeBasket.contents should be("Citrus")
|
||||||
|
val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Citrus](new Orange)
|
||||||
|
tangeloBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
val orangeBasketReally: MyContainer[Orange] = tangeloBasket.asInstanceOf[MyContainer[Orange]]
|
||||||
|
orangeBasketReally.contents should be("Citrus")
|
||||||
|
orangeBasketReally.set(new Orange())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declaring contravariance variance with - also means that the container cannot be accessed with a getter or
|
||||||
|
// or some other accessor, since that would cause type inconsistency. In our example, you can put an orange
|
||||||
|
// or a tangelo into a citrus basket. Problem is, if you have a reference to an orange basket,
|
||||||
|
// and if you believe that you have an orange basket then you shouldn't expect to get a
|
||||||
|
// tangelo out of it.
|
||||||
|
koan("A reference to a parent type means you cannot anticipate getting a more specific type") {
|
||||||
|
|
||||||
|
class MyContainer[-A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
val orangeBasket: MyContainer[Orange] = new MyContainer[Citrus](new Tangelo)
|
||||||
|
orangeBasket.contents should be("Citrus")
|
||||||
|
val tangeloBasket: MyContainer[Tangelo] = new MyContainer[Citrus](new Orange)
|
||||||
|
tangeloBasket.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declaring neither -/+, indicates invariance variance. You cannot use a superclass
|
||||||
|
// variable reference (\"contravariant\" position) or a subclass variable reference (\"covariant\" position)
|
||||||
|
// of that type. In our example, this means that if you create a citrus basket you can only reference that
|
||||||
|
// that citrus basket with a citrus variable only.
|
||||||
|
|
||||||
|
koan("invariance means you need to specify the type exactly") {
|
||||||
|
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
koan("""Declaring a type as invariant also means that you can both mutate and access elements from an object of generic type""") {
|
||||||
|
|
||||||
|
class MyContainer[A](a: A)(implicit manifest: scala.reflect.Manifest[A]) {
|
||||||
|
private[this] var item = a
|
||||||
|
|
||||||
|
def set(a: A) {
|
||||||
|
item = a
|
||||||
|
}
|
||||||
|
|
||||||
|
def get = item
|
||||||
|
|
||||||
|
def contents = manifest.erasure.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
val citrusBasket: MyContainer[Citrus] = new MyContainer[Citrus](new Orange)
|
||||||
|
|
||||||
|
citrusBasket.set(new Orange)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
|
||||||
|
citrusBasket.set(new Tangelo)
|
||||||
|
citrusBasket.contents should be("Citrus")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import support.KoanSuite
|
||||||
|
import support.BlankValues._
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class AboutUniformAccessPrinciple extends KoanSuite with ShouldMatchers {
|
||||||
|
|
||||||
|
class CalculatesAgeUsingMethod(var currentYear: Int, birthYear: Int) {
|
||||||
|
|
||||||
|
def age = currentYear - birthYear
|
||||||
|
|
||||||
|
// calculated when method is called
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalculatesAgeUsingProperty(var currentYear: Int, birthYear: Int) {
|
||||||
|
// does age stay up to date if defined as a var instead of a val?
|
||||||
|
val age = currentYear - birthYear
|
||||||
|
// calculated at instantiation, returns property when called
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Can access age as parameterless method") {
|
||||||
|
val me = new CalculatesAgeUsingMethod(2010, 2003)
|
||||||
|
me.age should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Can access age as property") {
|
||||||
|
val me = new CalculatesAgeUsingProperty(2010, 2003)
|
||||||
|
me.age should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("Cannot add parameter to Method invocation") {
|
||||||
|
val me = new CalculatesAgeUsingMethod(2010, 2003)
|
||||||
|
// uncomment following line to see what happens if you try to access parameterless method with parens
|
||||||
|
//me.age() should be (7)
|
||||||
|
}
|
||||||
|
koan("What happens when I update current year using property") {
|
||||||
|
val me = new CalculatesAgeUsingProperty(2010, 2003)
|
||||||
|
|
||||||
|
|
||||||
|
me.currentYear = 2011
|
||||||
|
me.age should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("What happens when I update current year using method") {
|
||||||
|
val me = new CalculatesAgeUsingMethod(2010, 2003)
|
||||||
|
|
||||||
|
|
||||||
|
me.currentYear = 2011
|
||||||
|
me.age should be(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.matchers.ShouldMatchers
|
||||||
|
import support.KoanSuite
|
||||||
|
|
||||||
|
class AboutValAndVar extends KoanSuite with ShouldMatchers {
|
||||||
|
koan("vars may be reassigned") {
|
||||||
|
var a = 5
|
||||||
|
a should be(5)
|
||||||
|
|
||||||
|
a = 7
|
||||||
|
a should be(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("vals may not be reassigned") {
|
||||||
|
val a = 5
|
||||||
|
a should be(5)
|
||||||
|
|
||||||
|
// What happens if you uncomment these lines?
|
||||||
|
// a = 7
|
||||||
|
// a should be (5)
|
||||||
|
}
|
||||||
|
|
||||||
|
koan("vals or vars can have the same name as a keyword as long as it's surrounded by `") {
|
||||||
|
val `class` = "MyClassName"
|
||||||
|
`class` should be("MyClassName")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package org.functionalkoans.forscala
|
||||||
|
|
||||||
|
import org.scalatest.Suite
|
||||||
|
import org.scalatest.junit.JUnitRunner
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(classOf[JUnitRunner])
|
||||||
|
class PathToEnlightenment extends Suite {
|
||||||
|
override def nestedSuites = List(new AboutAsserts,
|
||||||
|
new AboutValAndVar,
|
||||||
|
new AboutLiteralBooleans,
|
||||||
|
new AboutLiteralNumbers,
|
||||||
|
new AboutLiteralStrings,
|
||||||
|
new AboutConstructors,
|
||||||
|
new AboutTuples,
|
||||||
|
new AboutLists,
|
||||||
|
new AboutMaps,
|
||||||
|
new AboutSets,
|
||||||
|
new AboutSequencesAndArrays,
|
||||||
|
new AboutMutableMaps,
|
||||||
|
new AboutMutableSets,
|
||||||
|
new AboutOptions,
|
||||||
|
new AboutPatternMatching,
|
||||||
|
new AboutCaseClasses,
|
||||||
|
new AboutHigherOrderFunctions,
|
||||||
|
new AboutPartiallyAppliedFunctions,
|
||||||
|
new AboutPartialFunctions,
|
||||||
|
new AboutForExpressions,
|
||||||
|
new AboutEnumerations,
|
||||||
|
new AboutEmptyValues,
|
||||||
|
new AboutParentClasses,
|
||||||
|
new AboutNamedAndDefaultArguments,
|
||||||
|
new AboutInfixPrefixAndPostfixOperators,
|
||||||
|
new AboutInfixTypes,
|
||||||
|
new AboutAccessModifiers,
|
||||||
|
new AboutTypeSignatures,
|
||||||
|
new AboutTraits,
|
||||||
|
new AboutImportsAndPackages,
|
||||||
|
new AboutPreconditions,
|
||||||
|
new AboutUniformAccessPrinciple,
|
||||||
|
new AboutImplicits,
|
||||||
|
new AboutInteroperability,
|
||||||
|
new AboutManifests
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.functionalkoans.forscala.support
|
||||||
|
|
||||||
|
object BlankValues {
|
||||||
|
|
||||||
|
class ReplaceWithCorrectException extends Exception
|
||||||
|
|
||||||
|
val __ = "Should be filled in"
|
||||||
|
|
||||||
|
class ___ extends ReplaceWithCorrectException {
|
||||||
|
override def toString() = "___"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object Blankout {
|
||||||
|
def blank[T](t: T): T = t
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package org.functionalkoans.forscala.support
|
||||||
|
|
||||||
|
import org.scalatest.Reporter
|
||||||
|
import org.scalatest.Stopper
|
||||||
|
import org.scalatest.Distributor
|
||||||
|
import org.scalatest.Filter
|
||||||
|
import org.scalatest.Tracker
|
||||||
|
import org.scalatest.FunSuite
|
||||||
|
import org.scalatest.events.Event
|
||||||
|
import org.scalatest.events.TestSucceeded
|
||||||
|
|
||||||
|
trait KoanSuite extends FunSuite {
|
||||||
|
override def runTests(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter,
|
||||||
|
configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {
|
||||||
|
|
||||||
|
if (testName == null)
|
||||||
|
throw new NullPointerException("testName was null")
|
||||||
|
if (reporter == null)
|
||||||
|
throw new NullPointerException("reporter was null")
|
||||||
|
if (stopper == null)
|
||||||
|
throw new NullPointerException("stopper was null")
|
||||||
|
if (filter == null)
|
||||||
|
throw new NullPointerException("filter was null")
|
||||||
|
if (configMap == null)
|
||||||
|
throw new NullPointerException("configMap was null")
|
||||||
|
if (distributor == null)
|
||||||
|
throw new NullPointerException("distributor was null")
|
||||||
|
if (tracker == null)
|
||||||
|
throw new NullPointerException("tracker was null")
|
||||||
|
|
||||||
|
class KoanReporter(wrappedReporter: Reporter) extends Reporter {
|
||||||
|
var succeeded = false
|
||||||
|
|
||||||
|
override def apply(event: Event) = {
|
||||||
|
event match {
|
||||||
|
case e: TestSucceeded => succeeded = true
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
wrappedReporter(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stopRequested = stopper
|
||||||
|
|
||||||
|
// If a testName is passed to run, just run that, else run the tests returned
|
||||||
|
// by testNames.
|
||||||
|
testName match {
|
||||||
|
case Some(tn) => runTest(tn, reporter, stopRequested, configMap, tracker)
|
||||||
|
case None =>
|
||||||
|
val tests = testNames.iterator
|
||||||
|
var failed = false
|
||||||
|
for (test <- tests) {
|
||||||
|
if (failed == false) {
|
||||||
|
val koanReporter = new KoanReporter(reporter)
|
||||||
|
runTest(test, koanReporter, stopper, configMap, tracker)
|
||||||
|
failed = !koanReporter.succeeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def koan(name: String)(fun: => Unit) = test(name.stripMargin)(fun)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user