patternMinor
MarsRover in Scala
Viewed 0 times
scalamarsroverstackoverflow
Problem
This is my first attempt at Scala programming. I tried to be functional but I'm not sure if I have achieved that.
DataTypes.scala
RoverMachine.scala:
RoverApp.scala
```
import DataTypes._
import RoverMachine._
import scala.io.StdIn.readLine
object RoverApp extends App {
println("Enter Direction")
def parseToDirection(direction: String): Direction = {
direction match {
case "N" => N
case "S" => S
case "E" => E
case "W" => W
}
}
val initialDirection: Direction = parseToDirection(readLine())
println("Enter XPosition")
val initialXPosit
DataTypes.scala
object DataTypes {
case class Position(xPos: Int, yPos: Int)
sealed trait Direction
case object N extends Direction
case object S extends Direction
case object E extends Direction
case object W extends Direction
case class RoverState(direction: Direction, position: Position)
val leftRotation = Map[Direction, Direction](N -> W, W -> S, S -> E, E -> N)
val rightRotation = Map[Direction, Direction](N -> E, W -> N, S -> W, E -> S)
}RoverMachine.scala:
import DataTypes._
object RoverMachine {
def initialRoverState(direction: Direction, position: Position): RoverState = {
RoverState(direction, position)
}
def move(roverState: RoverState): RoverState = {
roverState.direction match {
case N => RoverState(N, Position(roverState.position.xPos, roverState.position.yPos + 1))
case S => RoverState(S, Position(roverState.position.xPos, roverState.position.yPos - 1))
case E => RoverState(E, Position(roverState.position.xPos + 1, roverState.position.yPos))
case W => RoverState(W, Position(roverState.position.xPos - 1, roverState.position.yPos))
}
}
def leftTurn(roverState: RoverState): RoverState = {
RoverState(leftRotation.get(roverState.direction).get, roverState.position)
}
def rightTurn(roverState: RoverState): RoverState = {
RoverState((rightRotation.get(roverState.direction)).get, roverState.position)
}
}RoverApp.scala
```
import DataTypes._
import RoverMachine._
import scala.io.StdIn.readLine
object RoverApp extends App {
println("Enter Direction")
def parseToDirection(direction: String): Direction = {
direction match {
case "N" => N
case "S" => S
case "E" => E
case "W" => W
}
}
val initialDirection: Direction = parseToDirection(readLine())
println("Enter XPosition")
val initialXPosit
Solution
Suggestions
-
For all your direction
You could try something like this:
and then your
However, this might complicate the code a bit and reduce the type safety offered by exhaustive pattern matching, however.
TL;DR Your approach with a
-
The one
-
-
(similarly for
-
Rely on type inference: In Scala, it is unnecessary to declare the types of most values, as they can be inferred by the compiler, and this does not decrease the readability of your code much unless the declaration is extremely complicated or the inferred type is unintuitive/incorrect.
This makes your value declarations in
Also, in
-
The code to move the rover should be a part of the
-
Drop the surrounding braces in one-line functions, it makes the code look more functional.
-
No exception handling - What to do if the user enters illegal characters? Scala is infamous for its confusing stack traces, it would be good to define code to handle exceptional conditions - a functional (recursive) equivalent of running the entire
-
Imports should be declared closest to their site of use.
-
There is code duplication between
Style
Naming:
You name your side-effect-free functions in a way which makes users think that they have side-effects. Instead of naming them with present-tense verbs, name them with past-tense verbs, like
Your indentation and bracket usage are spot-on unless otherwise noted.
Errors
-
In
The initial direction is always
Rant
You hadn't tried much to be functional. If you had, then not even a single
Most programmers hate global state, and for good reason. if there were any
-
For all your direction
case object hierarchy, you are basically trying to duplicate an enum, which Scala provides no language-level support for. However, Scala does provide support for enumerations at the API level via the Enumeration class, which should be extended by new enums.You could try something like this:
object Directions extends Enumeration {
type Direction = Value
val N, S, E, W = Value
}and then your
parseToDirection(String) method becomes unnecessary and can be replaced with a Direction.withName(String), a method for retrieving enum constants by name defined in Enumeration. You will also need to update your method signatures involving Direction to Directions.Direction, and add an import Directions._ before every piece of code in a given scope which uses a Direction, i.e., one import before declaring the rotation Maps, one in RoverMachine (this makes the get() call on the value unnecessary in the rotation methods), and one in RoverApp.However, this might complicate the code a bit and reduce the type safety offered by exhaustive pattern matching, however.
TL;DR Your approach with a
sealed trait and case objects is the preferred one in Scala, this is just an alternative. Note, you could also as well used a Java enum for this purpose too.-
The one
var in main() clouds the functional-ness of the code a bit. You could replace the loop with a recursive inner method in main, but there's always a better solution. Read to the end for a cohesive correction of your code.-
String in Scala can be treated to inherit Seq[Char], as String has an implicit conversion to StringOps, which is a subclass of Seq[Char], so you don't need to convert the instructionList to an Array[Char] before looping over it, you can use it directly as a String.-
Map.apply() is semantically the same as Map.get(), so use the former as it allows the syntactic sugar of making the entire call look like the following:def leftTurn(roverState: RoverState): RoverState = {
RoverState(leftRotation(roverState.direction), roverState.position)
}(similarly for
rightTurn(RoverState))-
Rely on type inference: In Scala, it is unnecessary to declare the types of most values, as they can be inferred by the compiler, and this does not decrease the readability of your code much unless the declaration is extremely complicated or the inferred type is unintuitive/incorrect.
This makes your value declarations in
main() look like (incorporating some of my other suggestions:val initialDirection = Directions.withName(readLine())
val initialXPosition = readLine().toInt
val initialYPosition = readLine().toInt
val instructionList = readLine()Also, in
DataTypes, you can skip the generic type specification for the Map[Direction,Direction] and write just Map, the compiler will infer the generic types.-
The code to move the rover should be a part of the
RoverMachine, not RoverApp. Also, it should be defined as a function like this:-
Drop the surrounding braces in one-line functions, it makes the code look more functional.
-
No exception handling - What to do if the user enters illegal characters? Scala is infamous for its confusing stack traces, it would be good to define code to handle exceptional conditions - a functional (recursive) equivalent of running the entire
main() block in a while loop which breaks only on legal input. Also, consider throwing proper exceptions in exceptional cases, an example is in point 6.-
Imports should be declared closest to their site of use.
-
There is code duplication between
leftTurn() and rightTurn(). You could probably use a turn() function accepting a type of turn, be it indicated by a Boolean flag, an enum constant (I use this approach in the code below), or a case object hierarchy. Pattern match on the type of turn to choose the correct rotations Map to use.Style
Naming:
You name your side-effect-free functions in a way which makes users think that they have side-effects. Instead of naming them with present-tense verbs, name them with past-tense verbs, like
turned instead of turn, moved instead of move, etc.Your indentation and bracket usage are spot-on unless otherwise noted.
Errors
-
In
main(), you initialize roverState as var roverState = initialRoverState(N, Position(initialXPosition, initialYPosition))The initial direction is always
N; although you accept the initial direction as an input, you never use it. That line should be var roverState = initialRoverState(initialDirection, Position(initialXPosition, initialYPosition))Rant
You hadn't tried much to be functional. If you had, then not even a single
var, mutable data structure or loop would have turned up in your code, but all 3 did, so...Most programmers hate global state, and for good reason. if there were any
vars in your DataTypes object, it would haveCode Snippets
object Directions extends Enumeration {
type Direction = Value
val N, S, E, W = Value
}def leftTurn(roverState: RoverState): RoverState = {
RoverState(leftRotation(roverState.direction), roverState.position)
}val initialDirection = Directions.withName(readLine())
val initialXPosition = readLine().toInt
val initialYPosition = readLine().toInt
val instructionList = readLine()object DataTypes {
case class Position(xPos: Int, yPos: Int)
object Directions extends Enumeration {
type Direction = Value
val N, S, E, W = Value
}
case class RoverState(direction: Directions.Direction, position: Position)
import Directions._
val leftRotation = Map(N -> W, W -> S, S -> E, E -> N)
val rightRotation = Map(N -> E, W -> N, S -> W, E -> S)
object Turns extends Enumeration {
type Turn = Value
val Left, Right = Value
}
}
object RoverMachine {
import DataTypes._
def initialRoverState(direction: Directions.Direction, position: Position) = Some(RoverState(direction, position))
import Directions._
def moved(state: Option[RoverState]) = state match {
case Some(roverState) => roverState.direction match {
case N => Some(RoverState(N, Position(roverState.position.xPos, roverState.position.yPos + 1)))
case S => Some(RoverState(S, Position(roverState.position.xPos, roverState.position.yPos - 1)))
case E => Some(RoverState(E, Position(roverState.position.xPos + 1, roverState.position.yPos)))
case W => Some(RoverState(W, Position(roverState.position.xPos - 1, roverState.position.yPos)))
}
case None => None
}
import Turns._
def turned(state: Option[RoverState], turn: Turns.Turn) = state match {
case Some(roverState) => Some(RoverState(turn match {
case Left => leftRotation(roverState.direction)
case Right => rightRotation(roverState.direction)
}, roverState.position))
case None => None
}
def travelled(state: Option[RoverState], instructions: Seq[Char]): Option[RoverState] = state match {
case Some(roverState) => instructions match {
case Seq() => state
case head +: tail => travelled(head match {
//also added support for lower case characters in the instructions String
case 'M'|'m' => moved(state)
case 'L'|'l' => turned(state, Turns.Left)
case 'R'|'r' => turned(state, Turns.Right)
case other => None
}, tail)
}
case None => None
}
}
object RoverApp extends App {
import DataTypes._
import RoverMachine._
import scala.io.StdIn.readLine
println("Enter Direction")
val initialDirection = Directions.withName(readLine())
println("Enter XPosition")
val initialXPosition = readLine().toInt
println("Enter YPosition")
val initialYPosition = readLine().toInt
println("Enter Instructions")
val instructions = readLine()
val state = travelled(initialRoverState(initialDirection, Position(initialXPosition, initialYPosition)), instructions)
state match {
case Some(roverState) => println(roverState.direction + " " + roverState.position.xPos + ", " + roverState.position.yPos)
case None => throw new IllegalArgumentException("There were illegal characters in the Command String")
}
}instructions match {
case Seq() => ... //matches the empty _Seq_uence
case head :+ tail => ... //binds the head of the _Seq_uence to `head` and the tail to `tail` (similarly to pattern matching on lists). Read below.
...
}Context
StackExchange Code Review Q#147487, answer score: 3
Revisions (0)
No revisions yet.