Instantly share code, notes, and snippets.

Embed
What would you like to do?
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)
}
}
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