Ben Wing | 16 Aug 2012 00:30
Favicon

Why aren't multiple implicit argument lists allowed?

I have created an argument-parsing library that I use in a number of projects.  The current definition of the method to specify an option with an associated value is as follows:

    def option[T](
      name1: String, name2: String = null, name3: String = null,
      name4: String = null, name5: String = null, name6: String = null,
      name7: String = null, name8: String = null, name9: String = null,
      default: T = null.asInstanceOf[T],
      metavar: String = null,
      choices: Seq[T] = null,
      help: String = "")
    (implicit convert: (String, String, ArgParser) => T, m: Manifest[T]) = {
      optionSeq[T](nonNullVals(name1, name2, name3, name4, name5, name6,
        name7, name8, name9),
        metavar = metavar, default = default, choices = choices,
        help = help)(convert, m)
    }

The first 9 arguments look a bit strange but the purpose is to allow a declaration like this:

  var k_best =
    ap.option[Int]("k-best", "k", "kb",
      default = 10,
      help = """Value of K for use in the mean-shift algorithm
(see '--coord-strategy').  For this value of K, we choose the K best cells
and then apply the mean-shift algorithm to the central points of those cells.

Default '%default'.""")

I.e. I can use either --k-best, -k or --kb as aliases for the same argument.

This works, but in practice I **really** want to declare it like this:

    def option[T](
      name1: String, name2: String = null, name3: String = null,
      name4: String = null, name5: String = null, name6: String = null,
      name7: String = null, name8: String = null, name9: String = null,
      default: T = null.asInstanceOf[T],
      metavar: String = null,
      choices: Seq[T] = null,
      help: String = "")
    (implicit convert: (String, String, ArgParser) => T)
    (implicit m: Manifest[T]) = {
      optionSeq[T](nonNullVals(name1, name2, name3, name4, name5, name6,
        name7, name8, name9),
        metavar = metavar, default = default, choices = choices,
        help = help)(convert, m)
    }

i.e. with two implicit argument lists.

This way the user can optionally provide their own conversion routine without having to muck around with explicitly supplying the manifest or other similar things that may be required internally but which aren't for end-user consumption.  The alternative of requiring the user to explicitly supply the manifest is ugly, esp. since the manifest itself is more or less a hack required to work around a Java bug, which in an ideal world shouldn't need to be exposed at all.

Why aren't multiple implicit argument lists allowed?  From a theoretical perspective there's little difference between curried and non-curried arguments, but I don't see any practical way to work around this problem -- the manifest cannot be retrieved inside of option() using manifest[T], because T has already been erased by then.


ben
Paolo Giarrusso | 20 Aug 2012 16:54
Picon
Gravatar

Re: Why aren't multiple implicit argument lists allowed?

Il giorno giovedì 16 agosto 2012 00:30:40 UTC+2, Ben Wing ha scritto:

I have created an argument-parsing library that I use in a number of projects.  The current definition of the method to specify an option with an associated value is as follows:

    def option[T](
      name1: String, name2: String = null, name3: String = null,
      name4: String = null, name5: String = null, name6: String = null,
      name7: String = null, name8: String = null, name9: String = null,
      default: T = null.asInstanceOf[T],
      metavar: String = null,
      choices: Seq[T] = null,
      help: String = "")
    (implicit convert: (String, String, ArgParser) => T, m: Manifest[T]) = {
      optionSeq[T](nonNullVals(name1, name2, name3, name4, name5, name6,
        name7, name8, name9),
        metavar = metavar, default = default, choices = choices,
        help = help)(convert, m)
    }

The first 9 arguments look a bit strange but the purpose is to allow a declaration like this:

  var k_best =
    ap.option[Int]("k-best", "k", "kb",
      default = 10,
      help = """Value of K for use in the mean-shift algorithm
(see '--coord-strategy').  For this value of K, we choose the K best cells
and then apply the mean-shift algorithm to the central points of those cells.

Default '%default'.""")

I.e. I can use either --k-best, -k or --kb as aliases for the same argument.

This works, but in practice I **really** want to declare it like this:

    def option[T](
      name1: String, name2: String = null, name3: String = null,
      name4: String = null, name5: String = null, name6: String = null,
      name7: String = null, name8: String = null, name9: String = null,
      default: T = null.asInstanceOf[T],
      metavar: String = null,
      choices: Seq[T] = null,
      help: String = "")
    (implicit convert: (String, String, ArgParser) => T)
    (implicit m: Manifest[T]) = {
      optionSeq[T](nonNullVals(name1, name2, name3, name4, name5, name6,
        name7, name8, name9),
        metavar = metavar, default = default, choices = choices,
        help = help)(convert, m)
    }

i.e. with two implicit argument lists.

This way the user can optionally provide their own conversion routine without having to muck around with explicitly supplying the manifest or other similar things that may be required internally but which aren't for end-user consumption.  The alternative of requiring the user to explicitly supply the manifest is ugly, esp. since the manifest itself is more or less a hack required to work around a Java bug, which in an ideal world shouldn't need to be exposed at all.

Why aren't multiple implicit argument lists allowed?  From a theoretical perspective there's little difference between curried and non-curried arguments, but I don't see any practical way to work around this problem -- the manifest cannot be retrieved inside of option() using manifest[T], because T has already been erased by then.
I'm not sure about the reason for this choice, but I do know a workaround: using Predef.implicitly in place of the implicit argument:

scala> def foo[T](a: String)(implicit b: Numeric[T], c: Ordering[T]) = b
foo: [T](a: String)(implicit b: Numeric[T], implicit c: Ordering[T])Numeric[T]

scala> foo[Int]("Hi")(implicitly, Ordering[Int])
res1: Numeric[Int] = scala.math.Numeric$IntIsIntegral$ <at> 7eb5666

implicitly is defined in Predef very simply and elegantly as:

def implicitly[T](implicit t: T) = t

In examples like the above one, type inference will infer T (above, T = Numeric[Int]), and implicit resolution will provide an implicit value of type T for the argument t.
In the above example you also see one advantage of this technique: the user can also easily pass just the second argument explicitly. With a second implicit argument list, instead, that would not be possible - not without implicitly. Still, I'm nobody to judge whether this would be a useful feature - I've been missing this feature and similar ones, too.

In your example above, instead, I would use currying for another problem - having name1... name9, and thus an arbitrary fixed number of possible names. Instead you could declare something like:

def option[T](name: String, otherNames: String*)
     (default: T = null.asInstanceOf[T],
      metavar: String = null,
      choices: Seq[T] = null,
      help: String = "")
    (implicit convert: (String, String, ArgParser) => T)
    (implicit m: Manifest[T]) = {
      optionSeq[T](name +: otherNames,
        metavar = metavar, default = default, choices = choices,
        help = help)(convert, m)
    }

--
Paolo Giarrusso - Ph.D. Student, Philipps-University Marburg
http://www.informatik.uni-marburg.de/~pgiarrusso/

Gmane