Last active
August 29, 2015 14:01
-
-
Save sebnozzi/c4824a1fb8bcc8a57aac to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object PositiveIntApp extends App { | |
// The class Option[T] allows us to eventually wrap around a value. | |
// Instances of Option[T] *could* have a value, or not. | |
// | |
// If they have a value, we are dealing with a Some[T], which is a | |
// subclass of Option[T]. | |
// | |
// If the don't have a value, we are dealing with a None, which is | |
// a special singleton instance of Option. | |
// | |
// This way, we can deal with cases where we either have something | |
// or not in a type-safe way. | |
/** | |
* Helper function to allow us to parse an Int type-safely. | |
* | |
* If the input can be parsed to a normal Int, a Some[Int] will be returned. | |
* Otherwise a None will be returned. | |
*/ | |
def safelyParse(nrStr: String): Option[Int] = { | |
try { | |
Some(nrStr.toInt) | |
} catch { | |
case e: NumberFormatException => None | |
} | |
} | |
/** | |
* This class will represent a positive-Int. It wraps around an Int | |
* value and will only be constructed if the wrapped value IS positive. | |
* | |
* Some things to note: | |
* | |
* 1) The constructor is private; we can't just construct a PositiveInt. | |
* 2a) Extends AnyVal: allocation & performance will be like real Int. | |
* 2b) Eiffel also has this trick. | |
*/ | |
class PositiveInt private (val value: Int) extends AnyVal {} | |
/** | |
* This declares an object instance. | |
* | |
* In this case, it's a very special object: it's the so called | |
* "companion object" of the class PositiveInt. | |
* | |
* From within this instance, some things are allowed that otherwise | |
* are not. Like invoking the private constructor of the corresponding | |
* companion class. | |
*/ | |
object PositiveInt { | |
/** | |
* If the nr was positive, a Some[PositiveInt] will be returned. | |
* Otherwise, a None will be returned. | |
*/ | |
def create(nr: Int): Option[PositiveInt] = { | |
if (nr > 0) | |
// Note: the companion object CAN access private constructors | |
Some(new PositiveInt(nr)) | |
else | |
None | |
} | |
} | |
// Note again: | |
// | |
// Creating a PositiveInt directly is NOT allowed by the | |
// compiler, since the constructor is private. This does not compile: | |
// | |
// new PositiveInt(4) | |
// | |
// Thus, we are forced to use the creation methods of the companion object. | |
// ====== Main program | |
print("Please give me a positive Int: ") | |
val userInput = Console.readLine() | |
val optInt = safelyParse(userInput) | |
optInt map { intValue => | |
val optPositiveInt = PositiveInt.create(intValue) | |
// Variant 1: in ONE method call handle both cases. | |
// Option.fold(ifNone)(ifSome) forces you to treat both cases and | |
// provide a course of action for each. In the second case your anonymous | |
// function is passed the valid unwrapped PositiveInt. | |
optPositiveInt.fold({ | |
println("Hey, that was not a positive Int! " + intValue) | |
})({ posInt => | |
println("Thanks! You gave me a positive Int: " + posInt.value) | |
}) | |
// Variant 2: treat only the positive case. | |
// In this case, again the compiler forces you to declare an | |
// anonymous function which gets an unwrapped PositiveInt. | |
// The anonymous function will ONLY be called if the input was correct. | |
optPositiveInt foreach { posInt => | |
println("Thanks! You gave me a positive Int: " + posInt.value) | |
} | |
// Variant 3: treat only the negative case. | |
// The anonymous function passed to getOrElse will only be | |
// called if the input was not correct. Note that it has no parameters. | |
optPositiveInt getOrElse { | |
println("Hey, that was not a positive Int! " + intValue) | |
} | |
// Variant 4: treat both cases with a combination of map and getOrElse. | |
// What we are handling here is the return value of safelyParse: the parsing from | |
// String to Int. | |
// Inside of the map block, we are passed the intValue, if the parsing was successful. | |
// Otherwise the getOrElse block will be called. | |
} getOrElse { | |
println("Hey! That was not even an Int! " + userInput) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalatest.Matchers | |
import org.scalatest.WordSpec | |
class PositiveIntTests extends WordSpec with Matchers { | |
import PositiveIntApp._ | |
"safelyParse" should { | |
"return None" when { | |
"given empty string" in { | |
safelyParse("") should be(None) | |
} | |
"given null" in { | |
safelyParse(null) should be(None) | |
} | |
"given text which is not a number" in { | |
safelyParse("hello") should be(None) | |
} | |
"given a number followed by text" in { | |
safelyParse("33blah") should be(None) | |
} | |
"given a decimal number" in { | |
safelyParse("12.56") should be(None) | |
} | |
} | |
"return the parsed number" when { | |
"given a valid positive number" in { | |
safelyParse("123") should be(Some(123)) | |
} | |
"given a valid negative number" in { | |
safelyParse("-123") should be(Some(-123)) | |
} | |
} | |
} | |
"A PositiveInt" should { | |
"NOT be created" when { | |
"the input is 0" in { | |
PositiveInt.create(0) should be (None) | |
} | |
"the input is a negative number" in { | |
PositiveInt.create(-123) should be (None) | |
} | |
} | |
"ONLY be created" when { | |
"the input is a positive number" in { | |
PositiveInt.create(123).map{ posInt => posInt.value} getOrElse(-1) should be (123) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment