Sergey Alaev
Sergey Alaev

Reputation: 3982

How to rewrite this code in Scala using a Functional Programming approach

Below is code snippet that does some URL normalization. How to rewrite it to use immutable variables only?

Of course, without making it larger or more complex.

private def normalizeUrl(url0: String) = {
  var url = url0

  if (url.endsWith("/")) {
    url = url.dropRight(1)
  }

  if (url.indexOf(':') < 0 || 
      url.indexOf(':') == 1) { //windows absolute path
    url = "file:" + url;
  }

  url = url.replaceAll("\\\\", "/");

  url
}

Upvotes: 4

Views: 214

Answers (6)

Michael Lorton
Michael Lorton

Reputation: 44436

Consider name of functional programming! The whole point is that you replace variables with functions.

private def normalizeProtocol(url: String) = 
   if (url.endsWith("/")) url.dropRight(1) else url

private def removeEndingSlash(url: String) = 
  if (url.indexOf(':') < 0 || 
    url.indexOf(':') == 1)  //windows absolute path
    "file:" + url
  else 
    url

private def replaceSlash(url: String) = 
  url.replaceAll("\\\\", "/");

private def normalizeUrl(url: String) = 
  replaceSlash(normalizeProtocol(removeEndingSlash(url)))

CM Baxter points out, the last function could also be written as

private val normalizeUrl = 
   removeEndingSlash _ andThen 
   normalizeProtocol andThen 
   replaceSlash

I leave it to you to decide which is more legible.

Upvotes: 5

Angelo Genovese
Angelo Genovese

Reputation: 3398

One option would be to use the Reader Monad and map the functions over it:

val normalizeUrl: Reader[String, String] = Reader[String, String](s => s)
  .map(url => if (url.endsWith("/")) { url.dropRight(1) } else url)
  .map(url => if (url.indexOf(':') < 0 || url.indexOf(':') == 1) "file:" + url else url)
  .map(url => url.replaceAll("\\\\", "/"))

Then just call it like any function:

normalizeUrl("some\\\\url")

I think I would prefer elm's solution though

Upvotes: 2

cmbaxter
cmbaxter

Reputation: 35463

If you wanted to chain a bunch of these if/then conditions together to modify a string you could consider adding an implicit class to handle the if/then evaluations like so:

object UrlPimping{
  implicit class PimpedUrl(val url:String) extends AnyVal{
    def changeIf(condition:String => Boolean)(f:String => String):String = {
      if (condition(url)) f(url)
      else url      
    }
  }
}

private def normalizeUrl(url: String) = {
  import UrlPimping._

  url.
    changeIf(_.endsWith("/"))(_.dropRight(1)).
    changeIf(u => u.indexOf(':') < 0 || u.indexOf(':') == 1)(u => s"file:$u").
    replaceAll("\\\\", "/")
}

This would be overkill if you only had these two conditions to evaluate, but might be nice if you had more and this was a common pattern.

Upvotes: 10

Soumya Simanta
Soumya Simanta

Reputation: 11751

Here is another one. You write tests for reach of your functions ( removeEndSlash, removeSlashes and removeSlashes

def normalizeURL(url: String) = {
    def removeEndSlash(u: String): String = if (u.endsWith("/")) u.dropRight(1) else u
    def isFile(u: String): String = {
      val idx = u.indexOf(':')
      if (idx < 0 || idx == 1)
        "file:" + u
      else
        u
    }
    def removeSlashes( u : String ) = u.replaceAll("\\\\", "/")

    removeSlashes(isFile(removeEndSlash(url)))

  }

Upvotes: 0

What about some refactoring and chaining? Your var url is not necessary here.

I think this would work:

private def normalizeUrl(url: String) = {
  (if (url.indexOf(':') < 0 || url.indexOf(':') == 1) {
    "file:"
  } else {
    ""
  }) + (if (url.endsWith("/")) {
    url.dropRight(1)
  } else {
    url
  }).replaceAll("\\\\", "/")
}

Of course, for better readability, I would suggest the use of something like this:

private def normalizeUrl(url: String) = {
  val prefix  = if (url.indexOf(':') < 0 || url.indexOf(':') == 1) "file:" else ""
  val noSlash = if (url.endsWith("/")) url.dropRight(1) else url

  (prefix + noSlash).replaceAll("\\\\", "/")
}

Don't be afraid to use more than one val's. :)

Upvotes: 2

elm
elm

Reputation: 20415

Consider for instance an intuitive approach with intermediate results, and if-else expressions,

private def normalizeUrl(url0: String) = {
  val url1 = 
    if (url0.endsWith("/")) url0.dropRight(1) 
    else url0
  val url2 = 
    if (url1.indexOf(':') < 0 || url1.indexOf(':') == 1) "file:" + url1
    else url1

  url2.replaceAll("\\\\", "/")
}

Note the last expression, url2 with replaceAll, is returned.

Upvotes: 2

Related Questions