Two different uses of implicit parameters in Scala?
Asked Answered
I

2

5

(I am fairly new to Scala, hope this isn't a stupid question.)

From what I can see, declaring a parameter to a function implicit has two (related, but quite different) uses:

  1. It makes explicitly passing a corresponding argument when calling the given function optional when the compiler can find a unique suitable value to pass (in the calling scope).

  2. It makes the parameter itself a suitable value to pass to other functions with implicit parameters (when calling them from within the given function).

In code:

def someFunction(implicit someParameter: SomeClass) = {  // Note `implicit`
  ...
  // Note no argument supplied in following call;
  // possible thanks to the combination of
  // `implicit` in `someOtherFunction` (1) and
  // `implicit` in line 1 above (2)
  someOtherFunction
  ...
}

def someOtherFunction(implicit someOtherParameter: SomeClass) = {
  ...
}

implicit val someValue = new SomeClass(...)
// Note no argument supplied in following call;
// possible thanks to `implicit` (1)
someFunction

This seems somewhat strange, doesn't it? Removing implicit from line 1 would make both calls (to someFunction from some other place, and to someOtherFunction from within someFunction) not compile.

What is the rationale behind this? (Edit: I mean what is the official rationale, in case any can be found in some official Scala resource.)

And is there a way to achieve one without the other (that is to allow passing an argument to a function implicitly without allowing it to be used implicitly within that function when calling other functions, and/or to use a non-implicit parameter implicitly when calling other functions)? (Edit: I changed the question a bit. Also, to clarify, I mean whether there is a language construct to allow this - not achieving the effect by manual shadowing or similar.)

Isochronize answered 26/12, 2018 at 11:23 Comment(2)
I think the 2nd, clarified, question has been answered by the fact that so much code contortion was required to demonstrate the requested conversions/restrictions.Windmill
"Also, to clarify, I mean whether there is a language construct to allow this" No, there isn't.Berstine
B
4

For the first question

What is the rationale behind this?

answers are likely to be opinion-based.

And is there a way to achieve one without the other?

Yes, though it's a bit trickier than I thought initially if you want to actually use the parameter:

def someFunction(implicit someParameter: SomeClass) = {
  val _someParameter = someParameter // rename to make it accessible in the inner block

  { 
    val someParameter = 0 // shadow someParameter by a non-implicit
    someOtherFunction // doesn't compile
    someOtherFunction(_someParameter) // passed explicitly
  }
}
Berstine answered 26/12, 2018 at 11:43 Comment(1)
Thanks, @Alexey. This makes sense, and answers what I asked, but does not quite answer what I meant to ask :) Edited the question to clarify.Isochronize
T
4

The rationale is simple:

  • What has been passed as explicit, stays explicit
  • What has been marked as implicit, stays implicit

I don't think that any other combination (e.g. implicit -> explicit, let alone explicit -> implicit) would be easier to understand. The basic idea was, I think, that one can establish some common implicit context, and then define whole bunch of methods that expect same implicit variables that describe the established context.

Here is how you can go from implicit to explicit and back:

  • Implicit -> implicit (default)

    def foo(implicit x: Int): Unit = {
      bar
    }
    
    def bar(implicit x: Int): Unit = {}
    
  • Explicit -> implicit:

    def foo(x: Int): Unit = {
      implicit val implicitX = x
      bar
    }
    
    def bar(implicit x: Int): Unit = {}
    
  • Implicit -> explicit: I would just use Alexey Romanov's solution, but one could imagine that if we had the following method in Predef:

    def shadowing[A](f: Unit => A): A = f(())
    

    then we could write something like this:

    def foo(implicit x: Int): Unit = {
      val explicitX = x
      shadowing { x =>
        // bar         // doesn't compile
        bar(explicitX) // ok
      }
    }
    
    def bar(implicit x: Int): Unit = {}
    

    Essentially, it's the same as Alexey Romanov's solution: we introduce a dummy variable that shadows the implicit argument, and then write the body of the method in the scope where only the dummy variable is visible. The only difference is that a ()-value is passed inside the shadowing implementation, so we don't have to assign a 0 explicitly. It doesn't make the code much shorter, but maybe it expresses the intent a little bit clearer.

Tail answered 26/12, 2018 at 12:39 Comment(1)
Thanks, @Andrey. This makes sense, and answers what I asked, but does not quite answer what I meant to ask :) Edited the question to clarify.Isochronize

© 2022 - 2024 — McMap. All rights reserved.