4
\$\begingroup\$

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 = HLB
asked Aug 8, 2013 at 6:09
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

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:

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)String

And then, using the slightly modified version above:

scala> transformString("blah")(toUpper, reverse)
res3: String = HALB

Of course, you can always use the literal, too:

scala> val reverse2 = (s : String) => s.reverse
reverse2: String => String = <function1>
scala> transformString("blah")(reverse2)
res4: String = halb

Or even provide the lambda in-place:

scala> transformString("blah")(_ + "1", _.reverse)
res9: String = 1halb

IMO, varargs here work nicely, and you can transform any sequence/collection into varargs by :_*; there are a few differences, though:

  1. def methods have to be partially applied in the list (e.g. : toUpper_ below)
  2. 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] => String

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 varargs to Seq, which - in turn - means you explicitly have to pass a Seq, List or something similar:

scala> blahArg(List(_.reverse))
res10: String = halb

3.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.

answered Aug 9, 2013 at 7:03
\$\endgroup\$
6
  • \$\begingroup\$ So there's no solution for error: missing parameter type for expanded function ((x1ドル) => x1ドル.reverse) when trying transformString("blah", List(_.reverse)) ? \$\endgroup\$ Commented Aug 9, 2013 at 11:35
  • \$\begingroup\$ Also, is there any behavioral difference between transformString("blah",_.toUpperCase) and transformString("blah")(_.toUpperCase) - or is it just style? \$\endgroup\$ Commented Aug 9, 2013 at 11:37
  • \$\begingroup\$ Lastly, how does disenvowel look? \$\endgroup\$ Commented Aug 9, 2013 at 11:38
  • \$\begingroup\$ @noahz edited to address your questions :) \$\endgroup\$ Commented Aug 9, 2013 at 11:58
  • \$\begingroup\$ Thanks, I also found a better (IMO) disenvowel too: def disenvowel (s: String) = s filterNot {Set("aeiou": _*)(_) } \$\endgroup\$ Commented Aug 9, 2013 at 17:34
1
\$\begingroup\$

Depending on the function definition, you can do this inline using a function literal (which is basically a lambda):

transformString("blah", List(s => s.toUpperCase, s => s.reverse))

In fact, you can make this even more concise using _:

transformString("blah", List(_.toUpperCase, _.reverse))

For larger functions like disenvowel (which I'd probably call removeVowels), it'd likely be better to define it as a separate function, since it's likely less readable as a function literal. The implementation itself looks fine to me.

answered Aug 8, 2013 at 12:57
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.