Reputation: 3982
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
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
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
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
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
Reputation: 729
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
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