Question

I'd like to be able to pass in a function to a class such that the function always returns a Boolean, but the number of arguments can be variable.

This is actually what I'm working with right now:

/**
  * Template class for any SecureSocial SecuredAction.
  */
class SomeAction[T](
    isSuccessful: (SecuredRequest[AnyContent], T) => Boolean, 
    success: Result, 
    failure: Result) extends SecureSocial {

      def toReturn = SecuredAction(WithProvider("google")) {
        implicit request => if (isSuccessful(request)) success else failure
      }

}

I'd like the isSuccessful argument to be a function that takes at least one argument of type SecuredRequest[AnyContent] and a zero or more arguments after that. Sometimes I'll pass in a function that only needs the request object, and sometimes I'll pass in a function that needs the request object and some other parameters.

After seeing this answer I looked at currying, which is the practice of transforming a multiple-argument function into a single-argument function that will return a function if any more arguments are needed. This sounds like it can solve my problem, but after reading up on the difference between partially applied functions and currying, I'm starting to think not? It looks like there is still a set number of eventual arguments...

I suppose successful currying would go like so:

class SomeAction[T](
    isSuccessful : ((SecuredRequest[AnyContent])(T) => Boolean) ,  // ERROR
    ... ) ... { ... }

This definitely isn't the right syntax.

or

class SomeAction[T](
    isSuccessful : (SecuredRequest[AnyContent] => Boolean) , 
    ... ) ... { ... }

with something done to isSuccessful in toReturn to uncurry the passed function.

Multiple questions that may lead me to the answer:

  • Is there some particular syntax for describing curried functions I don't know about?
  • Or I'd have a type (SecuredRequest[AnyContent] => Boolean) function, but do some sort of uncurrying on the isSuccessful(request) call?

Thanks for reading; I guess I'm just looking for uncurrying examples.

Was it helpful?

Solution 3

Got it.

/**
  * Template class for any SecureSocial SecuredAction that involves using user information.
  * @param isSuccessful: SecuredRequest[AnyContent] => Boolean
  */
class UserReqAction(
  isSuccessful: (SecuredRequest[AnyContent] => Boolean),
  success: Result, failure: Result) extends SecureSocial {
  def toReturn = SecuredAction(WithProvider("google")) {
    implicit request => if (isSuccessful(request)) success else failure
  }
}

/**
  * Template class for any SecureSocial SecuredAction that does not use user information.
  * @param isSuccessful: SecuredRequest[AnyContent] => Boolean
  */
class UserNotReqAction(isSuccessful: () => Boolean,
                       success: Result, failure: Result) extends SecureSocial {
  def toReturn = SecuredAction(WithProvider("google")) {
    implicit request => if (isSuccessful()) success else failure
  }
}

// not curried example
def makeNewThing = new UserReqAction(
  userOp.createThing,
  Ok(Json.toJson(Json.obj("success" -> true, "msg" -> "Thing successfully created"))),
  BadRequest("failed to create Thing")).toReturn

// curried example
def editThing(param1: _, param2: _, etc) = new UserReqAction(
  userOp.updateThing(param1, param2, etc), // this is the currying
  Ok(Json.toJson(Json.obj("success" -> true, "msg" -> "Thing successfully edited"))),
  BadRequest("failed to edit Thing")).toReturn

def updateThing(param1: _, param2: _, etc)(request: SecuredRequest[AnyContent] ) {...}

OTHER TIPS

If you have a method f of type (A*)B, then eta-expanded f _ is just Seq[A] => B.

Inside of f, the repeated parameter has type Seq[A] anyway, so it makes sense.

There's a famous ticket:

https://issues.scala-lang.org/browse/SI-4176

It's famous for the immediately deprecated option -Yeta-expand-keeps-star.

Here is an example of a method that takes a function with a star on the left of the arrow and invokes it.

So if by "any-arity" you mean "any number of Ts", for instance type T = (String, String), then varargs suffices.

object Test extends App {

  def f(g: (Int*) => Int) = g(1,2,3)

  def sum(is: Int*) = is.sum

  Console println f(sum _)

  def f2(g: (Int*) => Int, h: =>Seq[Int]): Int = g(h: _*)

  Console println f2(sum _, List(1,2,3))

  def f3(g: (Int*) => Int, is: Int*): Int = g(is: _*)

  Console println f3(sum _, 1,2,3)
}

You can do it if your functions take a variable number of arguments. This is needed because they don't know in advance how many arguments will the action supply to them. As others mentioned, a variadic function is translated as a function whose last argument is a sequence. So you can do something like this:

case class Action[T](f: (String, Seq[T]) => Boolean);

def testfn(s: String, args: Int*): Boolean = true;

new Action[Int](testfn _);

And if you need your functions to signal that they got a wrong number of arguments, you can let them return Option and perhaps add some helper functions for various arities, like

case class Action[T](f: (String, Seq[T]) => Option[Boolean]);

Then you can have helpers for different arities that convert a "normal" function into a variadic one returning None if it's given a wrong number of arguments:

// for 1 argument functions:
def toVar[T](f: (String, T) => Boolean)(s: String, xs: Seq[T]): Option[Boolean] =
  Some(xs).collect({ case Seq(x1) => f(s, x1) });
// for 2 argument functions:
def toVar[T](f: (String, T, T) => Boolean)(s: String, xs: Seq[T]): Option[Boolean] =
  Some(xs).collect({ case Seq(x1, x2) => f(s, x1, x2) });

def testfn(s: String, x1: Int, x2: Int): Boolean =
  (x1 == x2);

new Action[Int](toVar(testfn _));

You could even avoid toVar if you created specialized constructors for Action for different arities that just call toVar and the main constructor.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top