patternMinor
Improve scala functional string transformation
Viewed 0 times
improvescalafunctionaltransformationstring
Problem
How could this scala code be made more concise and/or idiomatic? Do I need to define each functions as a
val in order to pass them as values, or is there a shorthand for this? Also looking for feedback on the disenvowel function.// See: http://nurkiewicz.blogspot.com/2012/04/secret-powers-of-foldleft-in-scala.html
def transformString(s: String, fns: List[Function1[String,String]]) :String =
(s /: fns) { (s: String, f: Function1[String,String]) => f apply s }
val toUpper = (s: String) => s.toUpperCase
val reverse = (s: String) => s.reverse
val disenvowel = (s: String) => s filterNot { Set('a','e','i','o','u') contains _ }
// scala> transformString("blah",List(disenvowel))
// res175: String = blh
// scala> transformString("blah",List(disenvowel, toUpper))
// res176: String = BLH
// scala> transformString("blah",List(disenvowel, toUpper, reverse))
// res177: String = HLBSolution
If I might chime in briefly, it all looks fine, any modifications here are mostly a matter of taste and personal preference; for example, I'd make it a curried function with varargs:
And you don't need to define functions as literals, for example:
And then, using the slightly modified version above:
Of course, you can always use the literal, too:
Or even provide the lambda in-place:
IMO, varargs here work nicely, and you can transform any sequence/collection into varargs by
so:
[EDIT for the comments]
1 & 2. Oh, there is a difference. You can easily make a function partially applied if it's curried, not so much if it just takes in arguments. Also, you can make shorthand underscore syntax work after applying the function partially:
Note the trailing underscore and the return type: a function (with lower arity). Now you can pass that function somewhere else where the second argument can be supplied. You can't do that when taking in both arguments at once. That also means that your second parameter changed from
3.Removing vowels looks fine :) If that's contained inside an
def transformString(s : String)(functions : String => String*) =
functions.foldLeft(s) { (str, fun) => fun(str) }And you don't need to define functions as literals, for example:
scala> def toUpper(s : String) = s.toUpperCase
toUpper: (s: String)String
scala> def reverse(s : String) = s.reverse
reverse: (s: String)StringAnd then, using the slightly modified version above:
scala> transformString("blah")(toUpper, reverse)
res3: String = HALBOf course, you can always use the literal, too:
scala> val reverse2 = (s : String) => s.reverse
reverse2: String => String =
scala> transformString("blah")(reverse2)
res4: String = halbOr even provide the lambda in-place:
scala> transformString("blah")(_ + "1", _.reverse)
res9: String = 1halbIMO, varargs here work nicely, and you can transform any sequence/collection into varargs by
:_*; there are a few differences, though:defmethods have to be partially applied in the list (e.g. :toUpper_below)
- Underscore shorthand syntax won't work
so:
scala> transformString("blah ")(List(reverse2, (s : String) => s + "12", toUpper_):_*)
res21: String = " HALB12"[EDIT for the comments]
1 & 2. Oh, there is a difference. You can easily make a function partially applied if it's curried, not so much if it just takes in arguments. Also, you can make shorthand underscore syntax work after applying the function partially:
scala> def blahArg = transformString("blah")_
blahArg: Seq[String => String] => StringNote the trailing underscore and the return type: a function (with lower arity). Now you can pass that function somewhere else where the second argument can be supplied. You can't do that when taking in both arguments at once. That also means that your second parameter changed from
varargs to Seq, which - in turn - means you explicitly have to pass a Seq, List or something similar:scala> blahArg(List(_.reverse))
res10: String = halb3.Removing vowels looks fine :) If that's contained inside an
object, you can move the Set initialization to the object level, so it's only initialized once instead of on every function call, but that's nitpicking.Code Snippets
def transformString(s : String)(functions : String => String*) =
functions.foldLeft(s) { (str, fun) => fun(str) }scala> def toUpper(s : String) = s.toUpperCase
toUpper: (s: String)String
scala> def reverse(s : String) = s.reverse
reverse: (s: String)Stringscala> transformString("blah")(toUpper, reverse)
res3: String = HALBscala> val reverse2 = (s : String) => s.reverse
reverse2: String => String = <function1>
scala> transformString("blah")(reverse2)
res4: String = halbscala> transformString("blah")(_ + "1", _.reverse)
res9: String = 1halbContext
StackExchange Code Review Q#29516, answer score: 3
Revisions (0)
No revisions yet.