SanCoder
SanCoder

Reputation: 51

Immutable way to handle the state in scala

I just start my FP journey while learning Scala.

Now a requirement is to maintain a List[String] in an unfiltered web application. When a POST request is sent to an endpoint, the List should be updated from a file. And the list will be used when a GET request is sent to the same endpoint.

Now, I am trying to avoid using var to saving the List. I know sometimes we have to use var but just curious that is there an elegant way to deal with the case. I've tried using scalaz.State Iterator and Steam. But got stuck since I have no idea how to pass the current immutable state to the next request. Any suggestion, please?

def update = State( l => {
  retrieve(filepath) match {
    case Success(lines) => (lines.split("[,\n\r]").toVector.map (_.trim), true)
    case Failure(_) => {
      log.error(s"Cannot retrieve the file.")
      (l, false)
    }
  }
})

def isContained(message: String) = State(l => (l, l.exists(message.contains)))

/* assume the following get or post method will be invoked when GET or POST request is sent to the endpoint */

def post() = update(Vector.empty) // how can I pass the updated state to the get method

def get(msg: String): Boolean = isContained(msg)(???)._2

Then I don't know how can I pass the current state to the next visit as input without using var.

Upvotes: 1

Views: 433

Answers (1)

I See Voices
I See Voices

Reputation: 842

There is no free lunch. If you want to avoid mutability and avoid storing the state somewhere, you need to work with returned values.

State is no more than a function A => B (I simplified it a bit for a purpose), where A is initial state and B is the endresult

So in your case, the model would be looking like:

def post(newMessage: String, state: List[String]): List[String] = {
  newMessage :: state
}

def get(msg: String, state: List[String]): Boolean = {
  state.contains(msg)
}

As you can see here, you need to provide the current state to every post and get. Posts would just the add a new message from the file (put your business logic here), and return a new state. And for get, you need to provide current state, to be able to retrieve something you want. You can rewrite it this way:

def post(newMessage: String): List[String] ⇒ List[String] = state ⇒ {
  newMessage :: state
}

def get(msg: String): List[String] ⇒ Boolean = 
  _.contains(msg)

Notice that post returns you exactly A ⇒ A (where A = List[String]).

ScalaZ State gives you Monad for chaining the functions inside for comprehension as well as some additional conveniences (like map, gets, put etc..) . But essentially - the underlying model would be the same.

This code represents more precisely what State does:

  type YourListState = List[String] ⇒ List[String]

  def post(newMessage: String, state: YourListState): YourListState = li ⇒ {
    newMessage :: state(li)
  }

  def get(msg: String, state: YourListState): List[String] ⇒ Boolean = {
    state(_).contains(msg)
  }

This allows you to combine states and to provide initial value and "run" it whenever you want, not sure that you really need this.

Upvotes: 1

Related Questions