scalascopeimplicitpass-by-name

Hiding and scoping implicit variable creation in a DSL


When developing a DSL, what is the cleanest way to limit the scope of an implicit variable and simultaneously hide the fact that such an implicit variable is defined?

As an example, this is the desired behavior...

object External
{
    def funNeedingValue(implicit a : String)
    {
        println(a)
    }
}

object Main extends App
{
    useValue("Hi") {
        // Implicit string "Hi" is only defined in this block
        External.funNeedingValue // Prints "Hi"
    }

    External.funNeedingValue // Compilation error: No implicit String defined
}

The following get close, but do not have all of the desired properties...

// The following works, but does not hide the fact that there is an implicit  
// variable defined.

object Main extends App
{
    {
        implicit val implicitValue = "Hi"
        External.funNeedingValue // Prints "Hi"
    }

    External.funNeedingValue // Compilation error: No implicit String defined
}

// The following hides that there is an implicit variable defined, but breaks
// the scoping requirement and destroys thread safety.

abstract class Parent
{
    implicit var implicitValue = ""

    def useValue(valueToMakeImplicit : String)(f : => Unit)
    {
        implicitValue = valueToMakeImplicit
        f()
    }
}

class Child extends Parent
{
    def go()
    {
        useValue("Hi") {
            External.funNeedingValue // Prints "Hi"
        }

        External.funNeedingValue // Scoping issue: also prints "Hi"
    }
}

object Main extends App
{
    new Child().go()
}

// The following works, but is harder to read and still doesn't really
// hide the implicit value

object Main extends App
{
    def useValue(valueToMakeImplicit : String)(f : String => Unit)
    {
        f(valueToMakeImplicit)
    }

    useValue("Hi") { 
        implicit value : String => {
            External.funNeedingValue // Prints "Hi"
        }
    }

    External.funNeedingValue // Compilation error: No implicit String defined
}

Solution

  • You could make a macro which transforms

    useValue("Hi") {
        // Implicit string "Hi" is only defined in this block
        External.funNeedingValue // Prints "Hi"
    }
    

    into

    {
      implicit val iString: String = "Hi"
      External.funNeedingValue
    }
    

    I don't think it's possible to do better than your last example without macros.