From 1dff526dea87e212f43e6e834c0a3fc0ca71b9c6 Mon Sep 17 00:00:00 2001 From: Tommy Parnell Date: Sat, 2 Jun 2018 14:05:05 -0400 Subject: [PATCH] init --- .gitattributes | 1 + .gitignore | 20 +- .hg_archival.txt | 6 + .hgignore | 18 + ListOfKoans.txt | 30 + README-scala.txt | 1 + build.sbt | 7 + ideaboard.txt | 51 ++ project/plugins/build.sbt | 13 + sbt | 1 + sbt-launch.jar | 3 + sbt.bat | 2 + src/main/scala/org/functionalkoans/App.scala | 9 + .../forscala/SomeJavaClass.java | 22 + src/test/resources/TODO.txt | 43 ++ .../forscala/AboutAbstractTypes.scala | 112 ++++ .../forscala/AboutAccessModifiers.scala | 198 +++++++ .../forscala/AboutActors.scala | 131 +++++ .../forscala/AboutAsserts.scala | 32 + .../forscala/AboutCaseClasses.scala | 132 +++++ .../forscala/AboutClasses.scala | 43 ++ .../forscala/AboutConstructors.scala | 51 ++ .../forscala/AboutEmptyValues.scala | 67 +++ .../forscala/AboutEnumerations.scala | 132 +++++ .../forscala/AboutForExpressions.scala | 52 ++ .../forscala/AboutFormatting.scala | 28 + .../forscala/AboutHigherOrderFunctions.scala | 129 ++++ .../forscala/AboutImplicits.scala | 96 +++ .../forscala/AboutImportsAndPackages.scala | 158 +++++ .../AboutInfixPrefixAndPostfixOperators.scala | 62 ++ .../forscala/AboutInfixTypes.scala | 54 ++ .../forscala/AboutInteroperability.scala | 28 + .../forscala/AboutIterables.scala | 92 +++ .../forscala/AboutLazySequences.scala | 61 ++ .../functionalkoans/forscala/AboutLists.scala | 130 +++++ .../forscala/AboutLiteralBooleans.scala | 23 + .../forscala/AboutLiteralNumbers.scala | 89 +++ .../forscala/AboutLiteralStrings.scala | 70 +++ .../forscala/AboutLiterals.scala | 138 +++++ .../forscala/AboutManifests.scala | 35 ++ .../functionalkoans/forscala/AboutMaps.scala | 139 +++++ .../forscala/AboutMutableMaps.scala | 59 ++ .../forscala/AboutMutableSets.scala | 59 ++ .../AboutNamedAndDefaultArguments.scala | 88 +++ .../forscala/AboutOptions.scala | 130 +++++ .../forscala/AboutParentClasses.scala | 41 ++ .../forscala/AboutPartialFunctions.scala | 88 +++ .../AboutPartiallyAppliedFunctions.scala | 22 + .../forscala/AboutPatternMatching.scala | 118 ++++ .../forscala/AboutPreconditions.scala | 37 ++ .../functionalkoans/forscala/AboutRange.scala | 34 ++ .../forscala/AboutSequencesAndArrays.scala | 53 ++ .../functionalkoans/forscala/AboutSets.scala | 130 +++++ .../forscala/AboutTraits.scala | 399 +++++++++++++ .../forscala/AboutTraversables.scala | 550 ++++++++++++++++++ .../forscala/AboutTuples.scala | 35 ++ .../forscala/AboutTypeAliases.scala | 43 ++ .../forscala/AboutTypeBounds.scala | 275 +++++++++ .../forscala/AboutTypeProjections.scala | 107 ++++ .../forscala/AboutTypeSignatures.scala | 142 +++++ .../forscala/AboutTypeVariance.scala | 233 ++++++++ .../AboutUniformAccessPrinciple.scala | 55 ++ .../forscala/AboutValAndVar.scala | 30 + .../forscala/PathToEnlightenment.scala | 45 ++ .../forscala/support/BlankValues.scala | 19 + .../forscala/support/KoanSuite.scala | 63 ++ 66 files changed, 5362 insertions(+), 2 deletions(-) create mode 100644 .gitattributes create mode 100644 .hg_archival.txt create mode 100644 .hgignore create mode 100644 ListOfKoans.txt create mode 100644 README-scala.txt create mode 100644 build.sbt create mode 100644 ideaboard.txt create mode 100644 project/plugins/build.sbt create mode 100644 sbt create mode 100644 sbt-launch.jar create mode 100644 sbt.bat create mode 100644 src/main/scala/org/functionalkoans/App.scala create mode 100644 src/test/java/org/functionalkoans/forscala/SomeJavaClass.java create mode 100644 src/test/resources/TODO.txt create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutAbstractTypes.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutAccessModifiers.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutActors.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutAsserts.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutCaseClasses.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutClasses.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutConstructors.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutEmptyValues.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutEnumerations.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutForExpressions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutFormatting.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutHigherOrderFunctions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutImplicits.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutImportsAndPackages.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutInfixPrefixAndPostfixOperators.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutInfixTypes.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutInteroperability.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutIterables.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLazySequences.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLists.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLiteralBooleans.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLiteralNumbers.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLiteralStrings.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutManifests.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutMaps.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutMutableMaps.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutMutableSets.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutNamedAndDefaultArguments.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutOptions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutParentClasses.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutPartialFunctions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutPartiallyAppliedFunctions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutPatternMatching.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutPreconditions.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutRange.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutSequencesAndArrays.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutSets.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTraits.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTraversables.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTuples.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTypeAliases.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTypeBounds.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTypeProjections.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTypeSignatures.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutTypeVariance.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutUniformAccessPrinciple.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/AboutValAndVar.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/PathToEnlightenment.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/support/BlankValues.scala create mode 100644 src/test/scala/org/functionalkoans/forscala/support/KoanSuite.scala diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7c32d5f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.jar filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 9c07d4a..cb24d97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,18 @@ -*.class -*.log +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 diff --git a/.hg_archival.txt b/.hg_archival.txt new file mode 100644 index 0000000..7724295 --- /dev/null +++ b/.hg_archival.txt @@ -0,0 +1,6 @@ +repo: e433542a2a0d9eb9fbe1b548ca5c76d54d12cb4e +node: 769e22d30ec694417d909efedabe480b8c37d392 +branch: default +latesttag: null +latesttagdistance: 138 +changessincelatesttag: 143 diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..cb24d97 --- /dev/null +++ b/.hgignore @@ -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 diff --git a/ListOfKoans.txt b/ListOfKoans.txt new file mode 100644 index 0000000..8bc77c1 --- /dev/null +++ b/ListOfKoans.txt @@ -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 diff --git a/README-scala.txt b/README-scala.txt new file mode 100644 index 0000000..012691e --- /dev/null +++ b/README-scala.txt @@ -0,0 +1 @@ +Please see http://bitbucket.org/dickwall/scala-koans/wiki/Home for more details on scala-koans diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..75e4862 --- /dev/null +++ b/build.sbt @@ -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") diff --git a/ideaboard.txt b/ideaboard.txt new file mode 100644 index 0000000..2d9a544 --- /dev/null +++ b/ideaboard.txt @@ -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 = + +scala> res2.apply +res4: Int = 123 + + + + diff --git a/project/plugins/build.sbt b/project/plugins/build.sbt new file mode 100644 index 0000000..3c61498 --- /dev/null +++ b/project/plugins/build.sbt @@ -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)) +} diff --git a/sbt b/sbt new file mode 100644 index 0000000..7743640 --- /dev/null +++ b/sbt @@ -0,0 +1 @@ +java -Xmx512M -jar `dirname $0`/sbt-launch.jar "$@" diff --git a/sbt-launch.jar b/sbt-launch.jar new file mode 100644 index 0000000..999635d --- /dev/null +++ b/sbt-launch.jar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8af85f4ecc3d07a04fb35a9e056a95de2dd9fe260a1551ba9aca5d50b8d1010 +size 937683 diff --git a/sbt.bat b/sbt.bat new file mode 100644 index 0000000..473e158 --- /dev/null +++ b/sbt.bat @@ -0,0 +1,2 @@ +set SCRIPT_DIR=%~dp0 +java -Xmx512M -jar "%SCRIPT_DIR%sbt-launch.jar" %* diff --git a/src/main/scala/org/functionalkoans/App.scala b/src/main/scala/org/functionalkoans/App.scala new file mode 100644 index 0000000..359417c --- /dev/null +++ b/src/main/scala/org/functionalkoans/App.scala @@ -0,0 +1,9 @@ +package org.functionalkoans + +/** + * Hello world! + * + */ +object App extends scala.App { + println( "Hello World!" ) +} diff --git a/src/test/java/org/functionalkoans/forscala/SomeJavaClass.java b/src/test/java/org/functionalkoans/forscala/SomeJavaClass.java new file mode 100644 index 0000000..37e97c1 --- /dev/null +++ b/src/test/java/org/functionalkoans/forscala/SomeJavaClass.java @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * tel: 505.363.5832 + */ +public class SomeJavaClass { + public int findSizeOfRawType(List list) { + return list.size(); + } + + public int findSizeOfUnknownType(List list) { + return list.size(); + } +} diff --git a/src/test/resources/TODO.txt b/src/test/resources/TODO.txt new file mode 100644 index 0000000..614f3b3 --- /dev/null +++ b/src/test/resources/TODO.txt @@ -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 \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutAbstractTypes.scala b/src/test/scala/org/functionalkoans/forscala/AboutAbstractTypes.scala new file mode 100644 index 0000000..ca179df --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutAbstractTypes.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutAccessModifiers.scala b/src/test/scala/org/functionalkoans/forscala/AboutAccessModifiers.scala new file mode 100644 index 0000000..bafa404 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutAccessModifiers.scala @@ -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 { + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutActors.scala b/src/test/scala/org/functionalkoans/forscala/AboutActors.scala new file mode 100644 index 0000000..8a057bd --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutActors.scala @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * 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. + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutAsserts.scala b/src/test/scala/org/functionalkoans/forscala/AboutAsserts.scala new file mode 100644 index 0000000..c33fded --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutAsserts.scala @@ -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) + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutCaseClasses.scala b/src/test/scala/org/functionalkoans/forscala/AboutCaseClasses.scala new file mode 100644 index 0000000..c4edeea --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutCaseClasses.scala @@ -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") + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutClasses.scala b/src/test/scala/org/functionalkoans/forscala/AboutClasses.scala new file mode 100644 index 0000000..16c1eee --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutClasses.scala @@ -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 + } + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutConstructors.scala b/src/test/scala/org/functionalkoans/forscala/AboutConstructors.scala new file mode 100644 index 0000000..cef5581 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutConstructors.scala @@ -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 + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutEmptyValues.scala b/src/test/scala/org/functionalkoans/forscala/AboutEmptyValues.scala new file mode 100644 index 0000000..ef02ab0 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutEmptyValues.scala @@ -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") + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutEnumerations.scala b/src/test/scala/org/functionalkoans/forscala/AboutEnumerations.scala new file mode 100644 index 0000000..e4c40a0 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutEnumerations.scala @@ -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) + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutForExpressions.scala b/src/test/scala/org/functionalkoans/forscala/AboutForExpressions.scala new file mode 100644 index 0000000..282794f --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutForExpressions.scala @@ -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) + } + + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutFormatting.scala b/src/test/scala/org/functionalkoans/forscala/AboutFormatting.scala new file mode 100644 index 0000000..db45ed7 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutFormatting.scala @@ -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("\\") + } + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutHigherOrderFunctions.scala b/src/test/scala/org/functionalkoans/forscala/AboutHigherOrderFunctions.scala new file mode 100644 index 0000000..949e95e --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutHigherOrderFunctions.scala @@ -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) + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutImplicits.scala b/src/test/scala/org/functionalkoans/forscala/AboutImplicits.scala new file mode 100644 index 0000000..d91a4fe --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutImplicits.scala @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * 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") + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutImportsAndPackages.scala b/src/test/scala/org/functionalkoans/forscala/AboutImportsAndPackages.scala new file mode 100644 index 0000000..62e0578 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutImportsAndPackages.scala @@ -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) + } +} + diff --git a/src/test/scala/org/functionalkoans/forscala/AboutInfixPrefixAndPostfixOperators.scala b/src/test/scala/org/functionalkoans/forscala/AboutInfixPrefixAndPostfixOperators.scala new file mode 100644 index 0000000..8248f35 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutInfixPrefixAndPostfixOperators.scala @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * 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") + } + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutInfixTypes.scala b/src/test/scala/org/functionalkoans/forscala/AboutInfixTypes.scala new file mode 100644 index 0000000..c639100 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutInfixTypes.scala @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * 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") + } + + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutInteroperability.scala b/src/test/scala/org/functionalkoans/forscala/AboutInteroperability.scala new file mode 100644 index 0000000..b1fa6ad --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutInteroperability.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutIterables.scala b/src/test/scala/org/functionalkoans/forscala/AboutIterables.scala new file mode 100644 index 0000000..dca8e1a --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutIterables.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLazySequences.scala b/src/test/scala/org/functionalkoans/forscala/AboutLazySequences.scala new file mode 100644 index 0000000..14e4ae5 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLazySequences.scala @@ -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) + } + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLists.scala b/src/test/scala/org/functionalkoans/forscala/AboutLists.scala new file mode 100644 index 0000000..e63b2ab --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLists.scala @@ -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 +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLiteralBooleans.scala b/src/test/scala/org/functionalkoans/forscala/AboutLiteralBooleans.scala new file mode 100644 index 0000000..0575e2d --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLiteralBooleans.scala @@ -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) + } + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLiteralNumbers.scala b/src/test/scala/org/functionalkoans/forscala/AboutLiteralNumbers.scala new file mode 100644 index 0000000..24ec9ab --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLiteralNumbers.scala @@ -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") + } + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLiteralStrings.scala b/src/test/scala/org/functionalkoans/forscala/AboutLiteralStrings.scala new file mode 100644 index 0000000..cb33b4f --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLiteralStrings.scala @@ -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 + */ + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala b/src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala new file mode 100644 index 0000000..59fa0c8 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutLiterals.scala @@ -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 + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutManifests.scala b/src/test/scala/org/functionalkoans/forscala/AboutManifests.scala new file mode 100644 index 0000000..7d8ea37 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutManifests.scala @@ -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: http://www.evolutionnext.com + * email: dhinojosa@evolutionnext.com + * 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") + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutMaps.scala b/src/test/scala/org/functionalkoans/forscala/AboutMaps.scala new file mode 100644 index 0000000..225cadb --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutMaps.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutMutableMaps.scala b/src/test/scala/org/functionalkoans/forscala/AboutMutableMaps.scala new file mode 100644 index 0000000..854abe5 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutMutableMaps.scala @@ -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) + } + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutMutableSets.scala b/src/test/scala/org/functionalkoans/forscala/AboutMutableSets.scala new file mode 100644 index 0000000..d0b89b1 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutMutableSets.scala @@ -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) + } + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutNamedAndDefaultArguments.scala b/src/test/scala/org/functionalkoans/forscala/AboutNamedAndDefaultArguments.scala new file mode 100644 index 0000000..423d1c7 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutNamedAndDefaultArguments.scala @@ -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) + } +} + diff --git a/src/test/scala/org/functionalkoans/forscala/AboutOptions.scala b/src/test/scala/org/functionalkoans/forscala/AboutOptions.scala new file mode 100644 index 0000000..8c4e3d5 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutOptions.scala @@ -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 + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutParentClasses.scala b/src/test/scala/org/functionalkoans/forscala/AboutParentClasses.scala new file mode 100644 index 0000000..13f96e3 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutParentClasses.scala @@ -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) { + } + + } + } + + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutPartialFunctions.scala b/src/test/scala/org/functionalkoans/forscala/AboutPartialFunctions.scala new file mode 100644 index 0000000..a809625 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutPartialFunctions.scala @@ -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") + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutPartiallyAppliedFunctions.scala b/src/test/scala/org/functionalkoans/forscala/AboutPartiallyAppliedFunctions.scala new file mode 100644 index 0000000..92b70e3 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutPartiallyAppliedFunctions.scala @@ -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) + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutPatternMatching.scala b/src/test/scala/org/functionalkoans/forscala/AboutPatternMatching.scala new file mode 100644 index 0000000..df8651f --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutPatternMatching.scala @@ -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) + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutPreconditions.scala b/src/test/scala/org/functionalkoans/forscala/AboutPreconditions.scala new file mode 100644 index 0000000..0ecbc02 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutPreconditions.scala @@ -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) + + } + } +} + + + + + diff --git a/src/test/scala/org/functionalkoans/forscala/AboutRange.scala b/src/test/scala/org/functionalkoans/forscala/AboutRange.scala new file mode 100644 index 0000000..82adb35 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutRange.scala @@ -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) + } + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutSequencesAndArrays.scala b/src/test/scala/org/functionalkoans/forscala/AboutSequencesAndArrays.scala new file mode 100644 index 0000000..e70cf17 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutSequencesAndArrays.scala @@ -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")) + } + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutSets.scala b/src/test/scala/org/functionalkoans/forscala/AboutSets.scala new file mode 100644 index 0000000..c7434c0 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutSets.scala @@ -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) + } + + + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTraits.scala b/src/test/scala/org/functionalkoans/forscala/AboutTraits.scala new file mode 100644 index 0000000..d2a4f1a --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTraits.scala @@ -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") + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTraversables.scala b/src/test/scala/org/functionalkoans/forscala/AboutTraversables.scala new file mode 100644 index 0000000..f0194e0 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTraversables.scala @@ -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)) + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTuples.scala b/src/test/scala/org/functionalkoans/forscala/AboutTuples.scala new file mode 100644 index 0000000..509a5a5 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTuples.scala @@ -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) + } + + +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTypeAliases.scala b/src/test/scala/org/functionalkoans/forscala/AboutTypeAliases.scala new file mode 100644 index 0000000..322edb9 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTypeAliases.scala @@ -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 .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 .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") + } +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTypeBounds.scala b/src/test/scala/org/functionalkoans/forscala/AboutTypeBounds.scala new file mode 100644 index 0000000..3e71306 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTypeBounds.scala @@ -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 \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTypeProjections.scala b/src/test/scala/org/functionalkoans/forscala/AboutTypeProjections.scala new file mode 100644 index 0000000..05196c9 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTypeProjections.scala @@ -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 + | } +: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 + | } +:9: error: overriding type Y in trait X, which equals String; +type Y has incompatible type + override type Y = Int + ^ + + */ +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTypeSignatures.scala b/src/test/scala/org/functionalkoans/forscala/AboutTypeSignatures.scala new file mode 100644 index 0000000..a04e23f --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTypeSignatures.scala @@ -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) + } +} + diff --git a/src/test/scala/org/functionalkoans/forscala/AboutTypeVariance.scala b/src/test/scala/org/functionalkoans/forscala/AboutTypeVariance.scala new file mode 100644 index 0000000..cec2774 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutTypeVariance.scala @@ -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") + } +} diff --git a/src/test/scala/org/functionalkoans/forscala/AboutUniformAccessPrinciple.scala b/src/test/scala/org/functionalkoans/forscala/AboutUniformAccessPrinciple.scala new file mode 100644 index 0000000..a7f384c --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutUniformAccessPrinciple.scala @@ -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) + } + + +} \ No newline at end of file diff --git a/src/test/scala/org/functionalkoans/forscala/AboutValAndVar.scala b/src/test/scala/org/functionalkoans/forscala/AboutValAndVar.scala new file mode 100644 index 0000000..a6f453b --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/AboutValAndVar.scala @@ -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") + } + + +} diff --git a/src/test/scala/org/functionalkoans/forscala/PathToEnlightenment.scala b/src/test/scala/org/functionalkoans/forscala/PathToEnlightenment.scala new file mode 100644 index 0000000..6a1d0bf --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/PathToEnlightenment.scala @@ -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 + ) +} diff --git a/src/test/scala/org/functionalkoans/forscala/support/BlankValues.scala b/src/test/scala/org/functionalkoans/forscala/support/BlankValues.scala new file mode 100644 index 0000000..8455cf3 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/support/BlankValues.scala @@ -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 +} + diff --git a/src/test/scala/org/functionalkoans/forscala/support/KoanSuite.scala b/src/test/scala/org/functionalkoans/forscala/support/KoanSuite.scala new file mode 100644 index 0000000..b0d2e46 --- /dev/null +++ b/src/test/scala/org/functionalkoans/forscala/support/KoanSuite.scala @@ -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) +}