Skip to content

Commit

Permalink
API with a Functional Core
Browse files Browse the repository at this point in the history
  • Loading branch information
sungkmi committed Sep 9, 2016
1 parent 5036fef commit bc44831
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 1 deletion.
56 changes: 56 additions & 0 deletions src/main/scala/lascala/turtle/APIwithFunctionalCore.scala
@@ -0,0 +1,56 @@
package lascala.turtle

import lascala.turtle.common._
import scala.util.{Failure, Try}

object APIwithFunctionalCore extends App {
import TurtleApi._

class TurtleApi {

var state = initialTurtleState

def updateState(newState:TurtleState) = {
state = newState
}


def exec(commandStr: String) = {
val tokens = commandStr.split(' ').toList.map(_.trim)

val stateR = returnR(state)

val newStateR = tokens match {
case List("Move", distanceStr) =>
val distanceR = validateDistance(distanceStr)
//lift2R(move)(distanceR)(stateR)
}
}
}

object TurtleApi {
case class TurtleState(position: Position, angle: Angle, color: PenColor, penState: PenState)

lazy val initialTurtleState = TurtleState(initialPosition, 0.0.degree, initialColor, initialPenState)

def move(log: Log)(distance: Distance)(state: TurtleState) = {
log(f"Move $distance%.1f")
val newPosition = state.position.move(distance, state.angle)
if (state.penState == PenState.Down) dummyDrawLine(log, state.position, newPosition, state.color)
state.copy(position = newPosition)
}

def validateDistance(distanceStr: String): Try[Double] = {
Try(distanceStr.toDouble) recoverWith {
case e:NumberFormatException => Failure(InvalidDistance(distanceStr))
}
}


sealed trait ErrorMessage extends Throwable
case class InvalidDistance(msg: String) extends ErrorMessage
case class InvalidAngle(msg: String) extends ErrorMessage
case class InvalidColor(msg: String) extends ErrorMessage
case class InvalidCommand(msg: String) extends ErrorMessage
}
}
12 changes: 12 additions & 0 deletions src/main/scala/lascala/turtle/common/package.scala
@@ -1,5 +1,7 @@
package lascala.turtle

import scala.util.{Failure, Success, Try}

package object common {

type Distance = Double
Expand Down Expand Up @@ -40,4 +42,14 @@ package object common {
def dummyDrawLine(log: String => Unit, oldPos: Position, newPos: Position, color: PenColor): Unit =
log(s"...Draw line from $oldPos to $newPos using $color")

def returnR[A](a: A): Try[A] = Try(a)

def mapR[A, B](f: A => B): Try[A] => Try[B] = _ map f

def lift2R[A, B, C](f: A => B => C): Try[A] => Try[B] => Try[C] = {
(ta: Try[A]) => (tb: Try[B]) => for {
a <- ta
b <- tb
} yield f(a)(b)
}
}
28 changes: 28 additions & 0 deletions src/test/scala/lascala/turtle/APIwithFunctionalCoreTest.scala
@@ -0,0 +1,28 @@
package lascala.turtle

import scala.util.{Failure, Success, Try}

import lascala.turtle.common._
import APIwithFunctionalCore.TurtleApi._
import utest._

object APIwithFunctionalCoreTest extends TestSuite {
val tests = this {
'validateDistance {
"success case" - {
val result: Try[Double] = validateDistance("1.0")
assertMatch(result){ case Success(d: Double) if (d - 1.0).abs < 0.00001 => }
}
"failure case" - {
val result: Try[Double] = validateDistance("abc")
assertMatch(result){ case Failure(InvalidDistance(_)) => }
}
}

'exec {
//val api = new TurtleApi()
//api.exec("Move 100")
//assertMatch(api.state){ case TurtleState(Position(x, y), _, _, _) if (x-100).abs < 0.00001 => }
}
}
}
14 changes: 13 additions & 1 deletion src/test/scala/lascala/turtle/common/CommonTest.scala
@@ -1,6 +1,6 @@
package lascala.turtle.common

import lascala.turtle.common._
import scala.util.{Success, Try}

import utest._

Expand Down Expand Up @@ -28,5 +28,17 @@ object CommonTest extends TestSuite {
"Position#toString" - {
assert( Position(0.1, 0.1).toString == "(0.1, 0.1)" )
}

'retunR {
assert(returnR("abc") == Try("abc"))
}

'mapR {
assert(mapR((x:String) => x.toInt)(Try("10")) == Success(10))
}

'lift2R {
assert(lift2R((a: Int) => (b: Int) => a + b)(Try(1))(Try(2)) == Success(3))
}
}
}

0 comments on commit bc44831

Please sign in to comment.