Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala function transformation

Say I've got a function taking one argument

def fun(x: Int) = x

Based on that, I want to generate a new function with the same calling convention, but that'll apply some transformation to its arguments before delegating to the original function. For that, I could

def wrap_fun(f: (Int) => Int) = (x: Int) => f(x * 2)
wrap_fun(fun)(2) // 4

How might one go about doing the same thing, except to functions of any arity that only have the part of the arguments to apply the transformation to in common?

def fun1(x: Int, y: Int) = x
def fun2(x: Int, foo: Map[Int,Str], bar: Seq[Seq[Int]]) = x

wrap_fun(fun1)(2, 4) // 4
wrap_fun(fun2)(2, Map(), Seq()) // 4

How would a wrap_fun definition making the above invocations work look like?

like image 835
rafl Avatar asked Apr 18 '12 19:04

rafl


People also ask

What is the meaning of => in Scala?

=> is syntactic sugar for creating instances of functions. Recall that every function in scala is an instance of a class. For example, the type Int => String , is equivalent to the type Function1[Int,String] i.e. a function that takes an argument of type Int and returns a String .

Why use higher order functions in Scala?

One reason to use higher-order functions is to reduce redundant code. Let's say you wanted some methods that could raise someone's salaries by various factors. Without creating a higher-order function, it might look something like this: Scala 2.

What is the difference between method and function in Scala?

Difference between Scala Functions & Methods: Function is a object which can be stored in a variable. But a method always belongs to a class which has a name, signature bytecode etc. Basically, you can say a method is a function which is a member of some object.

How do you pass parameters to a Scala function?

Scala - Functions with Named Arguments Named arguments allow you to pass arguments to a function in a different order. The syntax is simply that each argument is preceded by a parameter name and an equals sign. Try the following program, it is a simple example to show the functions with named arguments.


3 Answers

This can be done in fairly straightforwardly using shapeless's facilities for abstracting over function arity,

import shapeless._
import HList._
import Functions._

def wrap_fun[F, T <: HList, R](f : F)
  (implicit
    hl :   FnHListerAux[F, (Int :: T) => R],
    unhl : FnUnHListerAux[(Int :: T) => R, F]) =
      ((x : Int :: T) => f.hlisted(x.head*2 :: x.tail)).unhlisted

val f1 = wrap_fun(fun _)
val f2 = wrap_fun(fun1 _)
val f3 = wrap_fun(fun2 _)

Sample REPL session,

scala> f1(2)
res0: Int = 4

scala> f2(2, 4)
res1: Int = 4

scala> f3(2, Map(), Seq())
res2: Int = 4

Note that you can't apply the wrapped function immediately (as in the question) rather than via an assigned val (as I've done above) because the explicit argument list of the wrapped function will be confused with the implicit argument list of wrap_fun. The closest we can get to the form in the question is to explicitly name the apply method as below,

scala> wrap_fun(fun _).apply(2)
res3: Int = 4

scala> wrap_fun(fun1 _).apply(2, 4)
res4: Int = 4

scala> wrap_fun(fun2 _).apply(2, Map(), Seq())
res5: Int = 4

Here the explicit mention of apply syntactically marks off the first application (of wrap_fun along with its implicit argument list) from the second application (of the transformed function with its explicit argument list).

like image 100
Miles Sabin Avatar answered Oct 03 '22 23:10

Miles Sabin


As usual in Scala, there's yet another way to achieve what you want to do.

Here is a take based on currying of the first argument together with the compose of Function1:

def fun1(x : Int)(y : Int) = x
def fun2(x : Int)(foo : Map[Int, String], bar : Seq[Seq[Int]]) = x

def modify(x : Int) = 2*x

The resulting types as REPL shows you will be:

fun1: (x: Int)(y: Int)Int
fun2: (x: Int)(foo: Map[Int,String], bar: Seq[Seq[Int]])Int
modify: (x: Int)Int

And instead of wrapping the functions fun1 and fun2, you compose them, as technically, they are now both Function1 objects. This allows you to make calls like the following:

(fun1 _ compose modify)(2)(5)
(fun2 _ compose modify)(2)(Map(), Seq())

Both of which will return 4. Granted, the syntax is not that nice, given that you have to add the _ to distinguish fun1's application from the function object itself (on which you want to call the compose method in this case).

So Luigi's argument that it is impossible in general remains valid, but if you are free to curry your functions you can do it in this nice way.

like image 21
Frank Avatar answered Oct 03 '22 23:10

Frank


Since functions taking different numbers of arguments are different, unrelated types, you cannot do this generically. trait Function1 [-T1, +R] extends AnyRef and nothing else. You will need a separate method for each arity.

like image 29
Luigi Plinge Avatar answered Oct 03 '22 23:10

Luigi Plinge