HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Converting imperative style user input menu to functional programming style

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
userprogramminginputstylemenuimperativefunctionalconverting

Problem

I am currently trying to teach myself scala by rewriting projects from my intro CS class, which was taught in C++. In addition to just learning the syntax I am trying to gain understanding of how functional programming works and what is considered good functional programming style.

Below I've included both the original code, which is a simple while loop that continues looping until the user selects the 'quit' option, and my attempt at a functional equivalent. Have I done this correctly? What could be improved?

Original imperative version in C++:

int main()
{
  int option = -1;

  while (option != 3)
  {        
    cout > option;
    if (option == 1)
    {
      cout << "selected 1" << endl;
    }
    else if (option == 2)
    {
      cout << "selected 2" << endl;
    }
    else if (option == 3)
    {
      cout << "selected quit.\n";
    }
    else
    {
      cout << "Sorry, that command is not recognized. ";
    }
  }
  return 0;  
}


My attempt at a functional version in scala:

def menu(option: Int) {
  println("""|Please select one of the following:
             |  1 - one 
             |  2 - two
             |  3 - quit""".stripMargin)
  if (option == 1) {
    println("selected 1")
    val opt = StdIn.readInt
    menu(opt)
  }
  else if (option == 2) {
    println("selected 2")
    val opt = StdIn.readInt
    menu(opt)
  }
  else if (option == 3) {
    println("selected quit")
  }
  else {
    println("Sorry, that command is not recognized")
  }
}

def main(args: Array[String]) {
  println("""|Please select one of the following:
             |  1 - one 
             |  2 - two
             |  3 - quit""".stripMargin)
  val opt = StdIn.readInt
  menu(opt)
}

Solution

One of the Scala features you need to get used to using is match - it is your friend. One of your very best friends.

Also, the way you have defined menu is recursive, with no bounding. In this particular case, it's probably not an issue, but it is, in general a Really Bad Idea.

Finally, you have repeated the printing of the menu and reading of the option. In general, one should follow Don't Repeat Yourself.

So how about something like the code below. Note, one of the rare cases tailor made for do...while.

def menu(option: Int): Boolean = {
  option match {
    case 1 =>
      println("selected 1")
      true
    case 2 =>
      println("selected 2")
      true
    case 3 =>
      println("selected quit")
      false
    case _ => // the else case
      println("Sorry, that command is not recognized")
      true
  }
}

def readOption: Int = {
  println("""|Please select one of the following:
             |  1 - one 
             |  2 - two
             |  3 - quit""".stripMargin)
  StdIn.readInt()
}

def main(args: Array[String]) {
  var opt = 0
  do {
    opt = readOption
  } while (menu(opt))
}


Alternately, if you wanted to something more functional, you could change menu to something like this:

val actionMap = Map[Int, () => Boolean](1 -> handleOne, 2 -> handleTwo, 3 ->handleThree)

def handleOne(): Boolean = {
  println("selected 1")
  true
}

def handleTwo(): Boolean = {
  println("selected 2")
  true
}

def handleThree(): Boolean = {
  println("selected quit")
  false
}

def menu(option: Int): Boolean = {
  actionMap.get(option) match {
    case Some(f) => f()
    case None =>
      println("Sorry, that command is not recognized")
      false
  }
}


Where actionMap maps each Int to a function that takes no parameters and returns a Boolean. In menu, we use option as the key into the map and then call the result (if there is one).

Code Snippets

def menu(option: Int): Boolean = {
  option match {
    case 1 =>
      println("selected 1")
      true
    case 2 =>
      println("selected 2")
      true
    case 3 =>
      println("selected quit")
      false
    case _ => // the else case
      println("Sorry, that command is not recognized")
      true
  }
}

def readOption: Int = {
  println("""|Please select one of the following:
             |  1 - one 
             |  2 - two
             |  3 - quit""".stripMargin)
  StdIn.readInt()
}

def main(args: Array[String]) {
  var opt = 0
  do {
    opt = readOption
  } while (menu(opt))
}
val actionMap = Map[Int, () => Boolean](1 -> handleOne, 2 -> handleTwo, 3 ->handleThree)

def handleOne(): Boolean = {
  println("selected 1")
  true
}

def handleTwo(): Boolean = {
  println("selected 2")
  true
}

def handleThree(): Boolean = {
  println("selected quit")
  false
}

def menu(option: Int): Boolean = {
  actionMap.get(option) match {
    case Some(f) => f()
    case None =>
      println("Sorry, that command is not recognized")
      false
  }
}

Context

StackExchange Code Review Q#84989, answer score: 7

Revisions (0)

No revisions yet.