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.


#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.