3

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.

Community
  • 1
  • 1
Meredith
  • 3,928
  • 4
  • 33
  • 58
  • 2
    Why not have your caller do that? If you ask for a function `SecuredRequest[AnyContent] => Boolean`, the caller can take care of partially applying any of the other arguments. It's not like you'd know what to pass in anyway, right? – Mysterious Dan Jul 25 '13 at 19:58
  • possible duplicate of [using variable length argument in scala](http://stackoverflow.com/questions/5079845/using-variable-length-argument-in-scala) – Richard Sitze Jul 25 '13 at 21:21
  • Thanks @Myserious Dan ! Yeah that solved my problem ^.^ – Meredith Jul 26 '13 at 08:40

3 Answers3

1

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)
}
Petr
  • 62,528
  • 13
  • 153
  • 317
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • I've seen this. I don't mean "any number of `T`s" though. I mean any number of anything, be it `String`, `boolean`, `Int`, ... – Meredith Aug 06 '13 at 23:31
  • @MeredithLeu I kind of figured when your answer popped up, which is why I was too lazy that night to format my answer, but thanks to petr-pudlak for doing that. – som-snytt Aug 07 '13 at 02:31
  • Users are automatically notified about comments on questions with their name in them, afaik. Thanks for taking the time to answer my question anyway. – Meredith Aug 07 '13 at 05:16
1

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.

Petr
  • 62,528
  • 13
  • 153
  • 317
  • thanks for the update, btw, see comment to my answer. SO doesn't let me send a msg to whom I wish to send it. – som-snytt Aug 07 '13 at 02:33
0

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] ) {...}
Meredith
  • 3,928
  • 4
  • 33
  • 58