Implicit Function Types


#42

Thanks a lot.

I think, “Static Scope Injection” is quite easy and enough. “Scope Injection” can be implemented with “Static Scope Injection” and “implicit function type”

I hope that one day “Static Scope Injection” will be implemented in dotty. It will make scala-dsl more powerful and more convenient.


#43

Yes, you end up with the identifiers in the global scope, also accessible from outside of the restricted context, so the encoding is not exactly equivalent. But IMHO it doesn’t warrant adding such a complex feature as scope injection, because you can have a nice error message when the construct is used outside of its scope and the implicit is not found, reading something like “this construct can only be used inside the scope of […] construct”, which I’d say is good enough.


#44

I’m interested, will it work with “Currying”:

def someFuntion(arg:SomeType)(implicit arg:SomeImplicitType)

In any case, the scope injection allow to:

  • Use third libraries
  • Minimize possible conflicts in global scope

#45

@mdedetrich Thanks for the reply which I just managed to see now (yikes). Your example is compelling and something I see frequently. Just to make sure I’m understanding, I think you’re saying that your example would be replaced as follows:

def doSomething(param: String)(implicit client: HttpExt, ec: ExecutionContext, materializer: Materializer) = ...

becomes:

def doSomething(param: String): Contextualized[...]

Where Contextualized essentially defines those implicit dependencies. This does cut down on the boilerplate, however I will say that I also see a pattern like this:

def doSomething(param: String)(implicit ctx: Context) = {
  implicit val ec = ctx.ec
  implicit val materializer = ctx.materializer
  // ...
}

I suppose this last variation also has boilerplate that gets stripped away in the declaration of the implicit vals, so it does save something. I suppose what I continue to question is the cost of muddying the waters between inputs and outputs, as discussed above.

I am getting the idea that implicit function types are useful for more than just cutting down on boilerplate, and that’s good. But originally I was responding to the stated motivation.


#46

For comparison, there is extension in kotlin:
see: https://kotlinlang.org/docs/reference/extensions.html
With this functionality we can make sql dsl:

fun search(name: String, minAge: Int?) = list {
    where {
        upper(c.name) like upper("%$name%")
        if (minAge != null) {
            and {
                c.age gte minAge
            }
        }
    }
}

In this example “where” is declared as:

fun where(op: WhereExpr.() -> Unit) =
            add(WhereExpr(this), op)

see:https://github.com/edvin/kdbc/blob/master/src/main/kotlin/kdbc/expression.kt

This is equivalent of scope injection.
It’s very convenient.


PRE SIP: ThisFunction | scope injection (similar to kotlin receiver function)
#47

Right, I can totally see both points. Scope injection is very convenient, but it also adds complexity to the language which may be unnecessary since you can also encode it. Having all values in the global scope (using the encoding) can be become more of problem when you start to have name clashes between identifiers.

Yes, currying should work, e.g., with the current plugin, you can write:

def someFuntion(arg:SomeType): Int `implicit =>` (SomeTypeT `implicit =>` SomeTypeU)

#48

Currying does not have overloading, for example:

class OverloadingTest extends FunSuite {
  def doNothing(a: Int)(implicit b:String): Unit = {}
  def doNothing(a: Int)(implicit b:BigDecimal): Unit = {}
  test("string") {
    implicit val  b: String = ""
    doNothing(1)
  }
}

compiled with:

Error:(13, 5) ambiguous reference to overloaded definition,

So, in global scope, there will be conflicts very quickly.

I have understood that scala has many complexity. And views(implicit val) make code quite surprising.
But, "import anorm._ " in the head of the file can be real headache. And there is no ability to easily overcome it now.
:frowning:


#49

I’m a little surprised to learn that there’s no syntax for implicit function values.

I was expecting that if given something like,

type Foo = implicit (Int, String) => implicit Boolean => Int

I would be able to write,

val foo: Foo =
  implicit (i: Int, s: String) => implicit (b: Boolean) =>
    s.length + i + (if(b) 1 else 0)

As it stands it seems that you’d either need to write methods to be eta expanded,

def foo1(implicit b: Boolean) = s.length + i + (if(b) 1 else 0)
def foo2(implicit i: Int, s: String): implicit Boolean => Int = foo1
val foo: Foo = foo2

or work with implicitly,

val blah: Foo =
  implicitly[String].length + implicitly[Int] + (if(implicitly[Boolean]) 1 else 0)

Neither of which are particularly pleasant.

I think we need to be able to name implicit function arguments if we’re going to be able to combine this feature cleanly with dependent function types. For instance, I think we ought to be able to write things along the lines of,

trait Bar { type T }
type Foo = implicit (b: Bar) => b.T => ...

More generally, I think we should be aiming to make implicit/dependent/polymorphic completely orthogonal, at least sufficiently to allow for a one-one correspondence between method types and function types.


#50

Good point. Implicit closures are supported (same as in Scala 2.12) but the syntax does not allow to give a parameter type. I.e. all you can do is:

implicit x => ...

We should generalize that to arbitrary closures.


#51

Alternatively, we can create non-implicit functions and implicitly apply it.

The alternative approach is implemented in a library called feature.scala:

Examples can be found in its Scaladoc.


#52

I think non-static scope injection is very nice feature, it is heavily exploited in Kotlin for convenient and easy
way to define builders/dsl and prove to be really powerful thing.
It is build on top of Katlin’s extension methods/ extension functions / extension lambadas
And in fact it is really close to

But use rather “extension” instead of “implicit” word
So Kotlin’s

ThisArg.() -> ResultType

Should become something like

ThisArg `extension import =>` ResultType

using DSL Paradise notation, or alternatively I personally would prefer something like

// without regular arguments - similar to Kotlin's `ThisArg.() -> ResultType`
(@this @import ThisArg) => ResultType
// or with them  - similar to Kotlin's `ThisArg.(RegularArg1, RegularArg2) -> ResultType`
(@this @import ThisArg, RegularArg1, RegularArg2) => ResultType

Also I think it could be very interesting to know that Kotlin also allows to resolve “scope injection clashes” via
marking different blocks/lambdas by labels: it is available as Qualified this expression

Brief demonstration of that Kotlin’s features may look like this:

Kotlin’s extension methods/functions/lambadas:

    fun testExtensions() {
        data class PointXY(val x: Int, val y: Int)

        // extension method/function
        fun PointXY.mullXY(): Int {
            // first argument of extension method passed through `this`
            assert(this is PointXY)
            assert(this.x == x)
            assert(this.y == y)
            return x * y;
        }

        // extension filed / local variable / method argument - member of extension function type initialized with extension lambada
        val addXY: PointXY.() -> Int = {
            // first argument of extension lambada passed through `this`
            assert(this is PointXY)
            assert(this.x == x)
            assert(this.y == y)
            x + y;
        }

        // usage
        val p0: PointXY = PointXY(10,6)
        assert(60 == p0.mullXY())
        assert(16 == p0.addXY())
    }

Kotlin’s resolution of hidden names (Qualified this expression):

    fun testExtensionsScopeResolution() {
        data class PointXY(val x: Int, val y: Int)
        data class PointYZ(val y: Int, val z: Int)

        val pXY = PointXY(1,2)
        val pYZ = PointYZ(3,4)

        // Kotlin( `with(obj) {...}` ) == Kotlin( `obj.apply {...}`  ) == Scala( `{import obj._; ... }` )
        with(pXY) labelOuterLambda@ {
            pYZ.apply labelInnerLambda@ {
                // `x` and `z` resolves without clashes
                assert(x == pXY.x)
                assert(z == pYZ.z)
                // `this` resolves to `pYZ` hover all outer `this`-es are also available via qualified notation
                assert(this@labelOuterLambda == pXY)
                assert(this@labelInnerLambda == pYZ && this == pYZ)
                // ambiguity of `y` resolution can be resolved by labels, by default it resolved in nearest scope
                assert(y == pYZ.y && y == this.y)
                assert(this@labelInnerLambda.y == pYZ.y)
                assert(this@labelOuterLambda.y == pXY.y)
            }
        }
    }

So I think it would be nice if Scala has something similar or (something better). In my opinion without scope injection in Scala, Kotlin will look much better then Scala in this area.


#53

Yes indeed this is correct

Yeah the point is that sometimes you can’t put all of the config variables inside one global config (or doing so is detrimental for other design reasons).