Using partial functions in Scala - how does it work?

21,994

Solution 1

A partial function is a function that is valid for only a subset of values of those types you might pass in to it. For example:

val root: PartialFunction[Double,Double] = {
  case d if (d >= 0) => math.sqrt(d)
}

scala> root.isDefinedAt(-1)
res0: Boolean = false

scala> root(3)
res1: Double = 1.7320508075688772

This is useful when you have something that knows how to check whether a function is defined or not. Collect, for instance:

scala> List(0.5, -0.2, 4).collect(root)   // List of _only roots which are defined_
res2: List[Double] = List(0.7071067811865476, 2.0)

This is not going to help you place two arguments where you really want one.

In contrast, a partially applied function is a function where some of its arguments have already been filled in.

def add(i: Int, j: Int) = i + j
val add5 = add(_: Int,5)

Now you only need one argument--the thing to add 5 to--instead of two:

scala> add5(2)
res3: Int = 7

You can see from this example how to use it.

But if you need to specify those two arguments, this still won't do it--say you want to use map, for instance, and you need to give it a function of one argument, but you want it to add two different things. Well, then you can

val addTupled = (add _).tupled

which will partially apply the function (really, just create a function out of the method, since nothing has been filled in) and then combine the separate arguments into a tuple. Now you can use this in places that require a single argument (assuming that the type is correct):

scala> List((1,2), (4,5), (3,8)).map(addTupled)
res4: List[Int] = List(3, 9, 11)

In contrast, currying is different yet again; it turns functions of the form (A,B) => C into A => B => C. That is, given a function of multiple arguments, it will produce a chain of functions that each take one argument and return a chain one shorter (you can think of it as partially applying one argument at a time).

val addCurried = (add _).curried

scala> List(1,4,3).map(addCurried)
res5: List[Int => Int] = List(<function1>, <function1>, <function1>)

scala> res5.head(2)   // is the first function, should add 1
res6: Int = 3

scala> res5.tail.head(5)   // Second function should add 4
res7: Int = 9

scala> res5.last(8)  // Third function should add 3
res8: Int = 11

Solution 2

Rex Kerr's explanation is very good -- and no surprise there either. The question is clearly mixing up partial functions with partially applied functions. For whatever it is worth, I made the same confusion myself when I learned Scala.

However, since the question does draw attention to partial functions, I'd like to speak of them a bit.

Many people say that partial functions are functions that are not defined for all input, and that's true of mathematics, but not of Scala. In Scala, a function may not be defined for all input either. In fact, since partial function inherit from function, then function includes all partial functions as well, making that inevitable.

Others mention the method isDefinedAt, which is, indeed, a difference, but one mostly about implementation. That's so true that Scala 2.10 will probably be released with a "fast partial function", which does not rely on isDefinedAt.

And a few people even imply that the apply method for partial functions do something different than the apply method for functions, like only executing for the input that is defined -- which could not be farther from the truth. The apply method is exactly the same.

What partial functions really come down to is another method: orElse. That sums up all use cases for partial functions much better than isDefinedAt, because partial functions are really about doing one of the following things:

  • Chaining partial functions (which is what orElse does), so that an input will be tried on each partial function until one of them matches.
  • Doing something different if a partial function doesn't match, instead of throwing an exception, which is what would happen if you chain that different thing using orElse.

I'm not saying everything can be easily implemented in terms of orElse, mind you. I'm just saying that partial functions are about doing something else when an input isn't defined for it.

Share:
21,994
PlexQ
Author by

PlexQ

I guess the term I'd use to represent me would be Technology Engineer. I've worked in a bunch of languages over the years covering typed languages like C, C++ and Java, weakly typed languages like Perl, Python (yeah yeah, it's not quite that simple) and PHP, functional languages like Lisp and Scala, web technologies like HTML and Javascript, document technologies like XML and PDF (It's worth mentioning because I wrote a library to read and write PDFs from scratch) file munging tools like sed, awk, grep and many others. I've done hardware configuration from whitebox to large x86 and x86_64 systems from vendors like HP and Dell, Blade systems, Network components from switches to security devices and edge routers running BGP. I've worked with Cisco IOS, PIX and ASA, Linux including RedHat, Ubuntu and SusE, Windows from NT4 onwards. I've done SQL RDBMS administration including Oracle, PostgreSQL, SQL Server and MySQL. These days I'm mostly doing Java development with Maven with a side of algorithm work. My math isn't as strong as I'd like, so I'm working on that right now. I'm a bit of an Apple fan, though I was late to the game. I own several iMacs, a MacBook Air a couple of Mac Minis, an iPhone, an iPad a few iPods two Apple TVs a Airport and a Time Capsule. I like that fact that I can turn it on and forget about the guts of it focusing purely on what I want to be doing instead of fighting my computer operating system and hardware all the time.

Updated on July 09, 2022

Comments

  • PlexQ
    PlexQ almost 2 years

    I'm new to Scala, I'm using 2.9.1, and I'm trying to get my head around how to use partial functions. I have a basic understanding of curried functions, and I know that partial functions are kind of like curried functions where they are only 2nary or some such. As you can tell I'm a bit green at this.

    It does seem that in certain cases like XML filtering, being able to partial functions would be highly advantageous, so I'm hoping get a better understanding of how to use them.

    I have a function that uses the RewriteRule structure, but I need it to work with two arguments, whereas the RewriteRule structure only takes one, OR a partial function. I think this is one of the cases I'm thinking about it being helpful.

    Any advice, links, words of wisdom etc. welcome!

    The answers so far are excellent, and have cleared up a few fundamental misconceptions I have. I think they also explain where I'm struggling - I think maybe posting a new question being a bit more specific will help, so I'll do that too.

  • cfischer
    cfischer almost 8 years
    great explanation! You should write a book on Scala! :-)
  • Jwan622
    Jwan622 almost 6 years
    Can you show us another example using the underscore without the .tuplied method? I'm still at a loss for what it does.
  • Rex Kerr
    Rex Kerr almost 6 years
    @Jwan622 - Why not play with it in the REPL? I bet you'll figure it out, as long as you understand the distinction between a method, e.g. def add(a: Int, b: Int) = a + b, and a function, e.g. val add: (a: Int, b: Int) => Int = (a, b) => a + b.