Partial Functions in Scala

I ran into a problem where i wanted to get a formatted string for each tuple in a list but ended on a detour to learning about partial functions. Consider this simple example:

scala> val wordCount = List(("a",1),("b",2))
wordCount: List[(java.lang.String, Int)] = List((a,1), (b,2))

scala> wordCount.map(wc => wc._1+":"+wc._2)
res4: List[java.lang.String] = List(a:1, b:2)

I can just use ._1 and ._2 on each tuple but it isn’t very readable. If someone else is reading the code, will they know what ._1 is for?

We can use a case statement to the rescue:
scala> wordCount.map(case(word,count) => word+":"+count)
:1: error: illegal start of simple expression
wordCount.map(case(word,count) => word+":"+count)
^

Opps, dpp mentioned on the scala mailing list that case needs to be a partial function so this syntax won’t work.

Partial Functions

Partial Functions are a subset of regular functions but can have a restricted input  range. The Partial Function trait has two abstract members: isDefinedAt(x:A):Boolean and apply(v1:A):B

A partial function which will only be called if the isDefinedAt method is true:

scala> object PartialDef extends PartialFunction[(String, Int), String]{
| def isDefinedAt(x:(String,Int)):Boolean = x._2 > 5
| def apply(x:(String,Int)):String = x._1 + ":" + x._2
| }
defined module PartialDef

A better way would be to use the case statement as this lets us use pattern matching to name arguments:

scala> val myRestrictedPartial:PartialFunction[(String,Int),String]= {case(word,count) if count > 5 => word+":"+count}
myRestrictedPartial: PartialFunction[(String, Int),String] = <function1>
scala> myRestrictedPartial("hi", 1)
scala.MatchError: (hi,1) (of class scala.Tuple2)
scala> myRestrictedPartial("hi", 10)
res1: String = hi:10

For our example, we don't need to define a restricted argument range so we'll remove the if count > 5:

scala> val myPartial:PartialFunction[(String, Int),String]= {case(word,count) => word+":"+count}
myPartial: PartialFunction[(String, Int),String] = <function1>

We can apply the partial function to each tuple explicitly:

scala> wordCount.map(x => myPartial(x))
res14: List[String] = List(a:1, b:2)

or with the implied argument:

scala> wordCount.map(myPartial)
res15: List[String] = List(a:1, b:2)

We can also pass the partial function as an anonymous function(or closure) as an argument:

scala> wordCount.map({case(word,count) => word+":"+count})
res16: List[java.lang.String] = List(a:1, b:2)

and remove the the extra parentheses:

scala> wordCount.map {case(word,count) => word+":"+count}
res17: List[java.lang.String] = List(a:1, b:2)

As we see, my original error was that I didn’t  realize the case statement needed to be nested in an anonymous function(the curly braces)  so that it could be passed into the map function as an argument.

Thanks to dpp for bringing partial functions to my attention while fixing a minor problem.

EDIT: I originally mistaken partial functions with partially applied functions. I removed the offending code to avoid confusion.

About tommychheng
I write a tech blog at http://tommy.chheng.com

4 Responses to Partial Functions in Scala

  1. Eddie Watson says:

    This article seems to conflate partially applied functions and partial functions. They are two distinct concepts with admittedly similar names.

  2. rahulj51 says:

    Eddie is right. Here’s a nice explanation of the difference between partial functions and partially applied functions : http://a-kovar.com/blog/2011/08/20/partial-functions-partially-applied-functions-and-currying/

  3. Change the parentheses to curly brackets:

    wordCount map {case(word, count) => word + “:” + count}

  4. I learned something.
    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

Gravatar
WordPress.com Logo

Please log in to WordPress.com to post a comment to your blog.

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.