Nearly everyone who learns Scala can get confused over the word partial used in the contexts:
So consider the function:
Now, let's partially apply this function by passing in some parameters and making another function.
A function is said to be partial if the function is only applied to a subset in set of element of x.
So if we only want to define the function
where x' = {1,2,3,4,5,6,7}
In Scala, a PartialFunction inherits from Function and adds two interesting methods:
- Partial functions
- Partially applied functions
Partially applied functions
Scala gets its functional ideas from classical languages such as Haskell (Haskell 1.0 appeared in same year as Depeche Mode's Enjoy the Silence and Dee Lite's Groove is in the Heart in 1990). In functional languages a function that takes two parameters that returns one parameter can be expressed as a function which takes one of the input parameters and returns a function that takes the other input parameter and returns the same output parameter.
f(x1, x2) = y
f(x1) = f(x2) = y
A cheesey analogy would be to time travel back to 1990 and find yourself a juxebox. Put money in for two selections and select Depeche Mode first and Dee Lite second, walk away and throw a few shapes as they are played one after the other. Or, put in your money for two selections select Depeche Mode and then don't make another selection. Don't walk away just yet. The well engineered Juxebox should prompt you for another selection (give you another function) and then you can select Dee Lite (pass in the second parameter). So, the end output in both cases is the same music in the same order.
In Scala, when only some parameters are passed to a function to make another function it is said to be a partial application of that function.
In Scala, when only some parameters are passed to a function to make another function it is said to be a partial application of that function.
So consider the function:
def minus(a: Int, b: Int) = "answer=" + (a-b)
val minus50 = (a: Int) => minus(a, 50);
In this case
minus50
is a partial application of minus.
We can do:
minus50(57); // outputs 7.
Note: we can also partially apply using the _ notation and a save ourselves a little bit of finger typing.
val minus50 = minus(_:Int, 50);
Partial functions
A partial function is a function that is valid for only a subset of values of those types you might pass into it. For example, Consider the mathematical function where x is set of all number from 1 to 100:
f(x) = x + 5;
So if we only want to define the function
f(x) = x + 5
for the numbers 1,2,3,4,5,6,7 but not 8,9,10, ... - we define a partial function.
f(x')=x+5
In Scala, a PartialFunction inherits from Function and adds two interesting methods:
isDefinedAt
- this allows us to check if a value is defined for the partial function.orElse
- this allows partial functions to be chained. So if a value is not defined for a function it can be passed to another function. This is similar to the GoF Chain of Responsibility pattern.
val add5Partial : PartialFunction[Int, Int] = { case d if (d > 0) && (d <= 7) => d + 5; }When you try this for a value less than or equal to 7, you will see the result no problem
scala > add5Partial(6); res1: 11When you try it for a value greater than 7 you don't get a nice clean answer.
scala> myPartial(42); scala.MatchError: 42 (of class java.lang.Integer) at $anonfun$1.apply$mcII$sp(:7) at . ( :9) at . ( ) at . ( :11) at . ( ) at $print( ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704) at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920) at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43) at scala.tools.nsc.io.package$$anon$2.run(package.scala:25) at java.lang.Thread.run(Thread.java:722)
The use of
isDefinedAt()
should now be becoming apparent. In this case, we could do:
add5Partial.isDefinedAt(4) res3: Boolean = true scala> add5Partial.isDefinedAt(42) res4: Boolean = falseOk so what about
orElse
? Well let's define another partial function which deals with numbers greater than 7 and less than 100. In such cases, lets' just add 4.
val add4Partial : PartialFunction[Int, Int] = { case d if (d > 7) && (d <= 100) => d + 5; }Now we can just do:
scala> val addPartial = add5Partial orElse add4Partial; addPartial : PartialFunction[Int,Int] = <function1> scala> addPartial(42); res6: Int = 46Ok, let's see how all this could be implemented in Java using Chain of Responsibility pattern. Firstly, let's define a handler interface and an add5 and add4 implementation which will implement it.
//Handler public interface AdditionHandler { //reference to the next handler in the chain public void setNext(AdditionHandler handler); //handle request public void handleRequest(int number); } public class Add5Handler implements AdditionHandler { private AdditionHandler nextAdditionHandler = null; public void setNext(AdditionHandler hander) { this.nextAdditionHandler = handler; } public int handleRequest(int number) { if ((number > 0) && (number <= 7)) { return number + 5; } else { return nextAdditionHandler.handleRequest(number); } } } public class Add4Handler implements AdditionHandler { private AdditionHandler nextAdditionHandler = null; public void setNext(AdditionHandler hander) { this.nextAdditionHandler = handler; } public int handleRequest(int number) { if ((number > 7) && (number <= 100)) { return number + 4; } else { return nextAdditionHandler.handleRequest(number); } } }Now, let's create a class which will link the handlers.
public class AdditionProcessor { private AdditionHandler prevHandler; public void addHandler(AdditionHandler handler){ if(prevHandler != null) { prevHandler.setNext(handler); } prevHandler = handler; } }And of a course a client which actually invokes the functionality:
public class AdditionClient { private AdditionProcessor processor; public AdditionClient(){ createProcessor(); } private void createProcessor() { processor = new AdditionProcessor(); processor.addHandler(new Add5Handler()); processor.addHandler(new Add4Handler()); } public void addRule(AdditionHandler handler) { processor.addHandler(handler); } public void requestReceived(int value){ System.out.println("value=" + processor.handleRequest(value)); } public static void main(String[] args) { AdditionClient client = new AdditionClient(); } }So Scala has some clear advantages here. Or course, people will say 'ah but in Java you just just do...'
public int addFunction(int value) { if ((value > 0) && (value <= 7)) { return value + 5; } else if ((value > 7) && (value < 100)) { return value + 4; } else { // ... } }And yes for this specific case, this will work. But what if your functions/ commands become more complex. Are you go to hang around in
if / else
land? Probably not. 'Til the next time, take care of yourselves.
No comments:
Post a Comment