diff --git a/src/main/scala/lascala/turtle/APIwithFunctionalCore.scala b/src/main/scala/lascala/turtle/APIwithFunctionalCore.scala new file mode 100644 index 0000000..3b89b2f --- /dev/null +++ b/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 + } +} diff --git a/src/main/scala/lascala/turtle/common/package.scala b/src/main/scala/lascala/turtle/common/package.scala index d13eb30..2177aaa 100644 --- a/src/main/scala/lascala/turtle/common/package.scala +++ b/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 @@ -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) + } } diff --git a/src/test/scala/lascala/turtle/APIwithFunctionalCoreTest.scala b/src/test/scala/lascala/turtle/APIwithFunctionalCoreTest.scala new file mode 100644 index 0000000..133534a --- /dev/null +++ b/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 => } + } + } +} diff --git a/src/test/scala/lascala/turtle/common/CommonTest.scala b/src/test/scala/lascala/turtle/common/CommonTest.scala index e99fc4d..034f4cd 100644 --- a/src/test/scala/lascala/turtle/common/CommonTest.scala +++ b/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._ @@ -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)) + } } }