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


#21

Receiver functions overcome it by not making you import anything, i.e., you do not introduce new identifiers into the block where you call the function that expects a receiver function (i.e. the block in which you call excelTable and htmlTable in my example), but only in the block of code that you pass to that function (in which we then call the row function).

The whole of the code would then become:

class ExcelTable {
  private def addRow(r: ExcelRow): Unit = ???
}
case class ExcelRow(text: String)
object ExcelTable {
  class Builder(val et: ExcelTable) {
    def row(text: String) = et.addRow(ExcelRow(text))
  }

  def excelTable(f: this Builder => Unit): ExcelTable = {
    val b = new Builder(new ExcelTable)
    f(b)
    b.et
  }
}
class HtmlTable {
  private def addRow(r: HtmlRow): Unit = ???
}
case class HtmlRow(text: String)
object HtmlTable {
  class Builder(val ht: HtmlTable) {
    def row(text: String) = ht.addRow(HtmlRow(text))
  }

  def htmlTable(f: this Builder => Unit): HtmlTable = {
    val b = new Builder(new HtmlTable)
    f(b)
    b.ht
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    import ExcelTable._
    val et = excelTable {
      row("I'm in Excel!")
    }

    import HtmlTable._
    val ht = htmlTable {
      row("I'm in HTML!")
    }
  }
}

(This could probably be minimized further, but it’s an extremely small edit distance)


#22

I Don’t like kotlin syntax for that. Maybe I’m just unfamiliar with it. Maybe.

Probably I need also to change name because of conflict with scala.js :frowning:.
Any ideas?
Ps: i tried to avoid ReceiverFunction[T,R] because don’t think it shows what it does. In kotlin it is function that can be called on T.

val init: HTML.() -> Unit
HTML().init() 

but in this proposal it is ordinary function that takes something special as first argument.


#23

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


#24

Yeah, you’re right. Kotlin makes it look like the it’s just another extension function for the receiver.


#25

This post was flagged by the community and is temporarily hidden.


#26

I’m not familiar with Scala Virtualized, but just from the intro, I don’t think it solves the problem at all.

When you try to create an internal DSL by working to make Scala not look like Scala, but more like SQL or human language, or something else, you are deploying a lot of elaborate machinery for little gain. It doesn’t scale. Seemingly innocuous changes require too much work. The abstraction leaks. Scala syntax is very flexible, but still not flexible enough. Scala is way too powerful, your DSL allows to express an infinite number of things it should not allow, and you cannot turn them off. And, oops, you cannot change operator precedence or associativity. You cannot change what is an assignment, or a numeric or String literal.

Either express what you want in idiomatic Scala, or if you want a DSL, write a parser.


#27

I’ve found that there’s a semantic disconnect when people talk about writing “DSLs” in Scala. Frankly, I think we should stop saying that it’s a good idea – at least in those terms.

Scala makes it relatively easy to add some kinds of language enhancements, which make it easier to do some kinds of things in Scala. I think of ScalaTest as the classic example: it provides a ton of “DSL” for testing.

But the key thing is, it’s not a separate language, it’s enhancements to Plain Old Scala. Within some fairly strong limits, Scala is decently good at that. But it’s not really a separate language, and trying to limit it to being a separate language seems like a lose.

So I honestly don’t think the term “DSL” really belongs here – it leads people to believe that they can build an entirely separate language inside Scala, which isn’t really true. (Even in specialized string interpolators, I’m not sure you can really turn off the Scala-ness.) What you can do is enhance the Scala language a bit, for some specialized purposes. When that’s what you want, great, and sometimes you can do remarkable stuff with it. (Eg, Rob’s BASIC-in-Scala monstrosity.) But it’s not really a true DSL…


#28

I think we will be able to achieve much more easy solution, If we allow such creation of builder.

for examle

//See java lambda expression 
@FunctionalInterface class Builder{

}
class HTML extend Builder{
    def body() { ... }
}

def html(html: HTML): HTML =  {
    html.set(...) //dependency injection
    html.apply()       //ThisFunction extends Function1 and we can call it as normal 
    html
}

html {    
    body()   // calling a method on the receiver object
}

If I understand correctly it can be just sugaring

html(
  new HTML {
      override def apply():Unit{
       ....
      }
  }
)

It also has advantage:
We can manual override HTML

html(
  new HTML {
     override def someMethod()...
     override def apply():Unit = {
       ....
      }
  }
)

I don’t see much troubles with that approach, and it is more scalable in some cases.


#29

Ok. I need reconsider whole idea. There is few steps that needs to be done before we go further:

  • looks to dsl-paradise and see all use cases.
  • see what can and what cannot be done with ImplicitFunctions
  • find alternative syntax for ThisFunction.
  • consider AMatveev proposition (still don’t understand this example but yea)

I hope to spend some time on it but still I’m waiting for opinions, corner cases and other stuff that was not been exposed yet. I understand that this idea is big and it can be harmful for language especially when is made wrong. It has been considered by many people before (dsl-paradise is great example) and it introduces possibilities that is hard to implement using existing features.

I agree. Most of the time when I talk about dsl i mean library that ‘fits’ into scala syntax but with nice and precise dictionary. ScalaTest doesn’t fit to this definition (in my opinion of course) because it tries to mimic English language (what is hard and leeks in many ways).

good examples of dsls are slick and scalatags. They lift scala language to provide great experience. You don’t need to know how implicit conversions, extensions methods and other stuff works to use them (you need to know few rules that are defined in documentation, and know scala a bit).

I founded way how to encode similar thing to this proposition using ImplicitFunction with bit more boilerplate. There is hope that this proposal could be not necessary. I need to test it more. Will see.


#30

It is similar to my idea but without wrapping everything with ThisFunction[T,R]. You just expects that Builder'll implement interface with apply() method that’ll be filled with anything in block.

  • + cost is potentially smaller. In both cases we need to create Builder or ThisFunction but in my proposition you’ll probably create T also (which is builder in your example).
  • - in my proposition ThisFunction is marker that say to compiler how to treat definition assigned to it (bit like PartialFunction i think). It could be simpler in implementation/ reason about.
  • - in your example you have not shown why HTML become inlined. We still need additional notation (like this notation from this proposal) to say to compiler that it should treat HTML differently.
//Why compiler decide to understand code like this:
html { body() }
//as shortcut for this
html( new HTML { override def apply():Unit = { body() }})
// instead of just expecting that some method body() returns HTML instance?
// what if there is method body() outside? what if it returns HTML instance?

#31

I can suggest an option with ThisFunction :wink:

  trait ThisFunction[-T, +R] extends Function1[T, R] 

  def html(htmFuncl: ThisFunction[HTML,R]): HTML =  {
    val html = htmlFunc()
    html.set(...) //dependency injection
    html.apply()       //ThisFunction extends Function1 and we can call it as normal 
    html
 }

The main difference is in a place where HTML class will be constructed, and how apply \ body function will be implemented.

I think in my proposal there is no need to define ThisFunction. There will be enough to define Builder with annotation @FunctionalInterface
We can see the implementation of this approach in java lambda

But I have not enough competencies in scala internals to prove my words, sorry.

I just do not understand why It could be simpler


#32
def html(htmFuncl: ThisFunction[HTML,R]): HTML =  {
   //in my proposition htmlFunc will be (HTML) => R
   //not () => R. This is the differences. You cannot just call it. 
   val html:HTML = htmlFunc()  
   html.set(...) //dependency injection
   //below comment is misleading. i guess it is just copy-paste artifact but still. 
   html.apply()       //ThisFunction extends Function1 and we can call it as normal 
   html
}

In your proposal we don’t need ThisFunction because T type has been constrained to satisfy some conditions (and we’ll fill it automatically based on that conditions). In my implementation you don’t constraint any T type and put whole magic into implementation ThisFunction instance. In your solution context is natural (you are using all fields/methods/implicit from type T because you are inside body of T and it is natural) in my proposal it isn’t.

This are big differences, but not sure what is better.


#33

May be there is no need for that constraint, I just assumed by analogy with the java.

I think advantages of my proposal are:

  • it is very simple to implement(compared to other),
  • we can manually create new anonymous classes if we need it

The disadvantage is

  • we cannot use custom \ external factories for the builders, so work with immutable classes is more difficult

So do I.
I prefer to have both option :), but it is too expensive

I will be happy with any implementation :wink:

But in such situations I prefer to choose more simple decision.


#34

I prefer to have one. There is no need to have few ways of same thing.

It is very likely that none of them’ll land in scala. Maybe we could mimic similar possibilities using ImplicitFunction. I’ve some ideas, more constrained but still. Problem is that such big feature could be too problematic in maintenance and reasoning especially with other new features like ImplicitFunction.

Martin tries to not introduce features that are not necessary and in long term can harm whole community. I believe that this feature is game changer for scala but again… I belive. We should measure it somehow.

Same thing apply in yours and my proposal. Each comes with it’s own weight. It needs to be measured/estimated.

What needs to be done is to check what useful things can be done with this feature and is nightmare without it. We can take few examples of successful libraries/apis from kotlin that heavily uses receiver functions and try to translate them to dotty to see if it is possible, similar or harder to express it.


#35

I had been thinking so for about five years.
But such checkpoints

give me hope

I remember how implicit function type was motivated.

Implicit function types are a surprisingly simple and general way to make coding patterns solving these tasks abstractable, reducing boilerplate code and increasing applicability.

So we just need to wait when DelayedInit will be deleted and there will appear enough boilerplate :slight_smile:


#36

You are not quite right since we have

  • implicit conversions from A to B that actually can bring A's names to the B's scope;
  • explicit this.type type that can be B in an implicit conversion above.

In current Scala it should look pretty wordy; it can look like this:

trait ThisFunction[A, +R] {
  def apply(a: A)(implicit s: this.type => A = {_ => a}): R
}

Then, having e.g.

trait SomeBuilder {
  def dup: Int
  def str: String
}

we can define (again, very wordy for now)

val f: ThisFunction[SomeBuilder, String] = new ThisFunction[SomeBuilder, String] {
  override def apply(a: SomeBuilder)(implicit s: this.type => SomeBuilder) =
    this.str * this.dup
}

As you can see, SomeBuilder's names are brought to the scope of defined “function” f (like you wanted). You can check that this really works.

(By the way, eliminating this in the body does not work, but it looks rather like a bug.)


But this is not exactly what was wanted. But, it seems that Dotty’s implicit function types really can solve this problem exactly in the manner that was requested.

(Notice that there is a special ImplicitConvertions type for implicit conversions in Dotty instead of just implicit function usage in Scala 2.)

We could have something like this in Dotty:

trait ThisFunction[A, +R] extends (A => (implicit ImplicitConverter[this.type, A] => R)) {
  override def apply(a: A)(implicit ic: ImplicitConverter[this.type, A] = { _ => a }): R
}

It means that definition of an instance of this type of function does not require a boilerplate with new ThisFunction and override def apply, so it could be done in a manner like this:

val f: ThisFunction[SomeBuilder, String] = { a: SomeBuilder => str * dup }

But at the moment it seems to be not working since at least strange currying rules: override def apply does not seem to override those apply from the A => (implicit ImplicitConverter[this.type, A] => R) type. This could probably be dictated by efficiency of compiled code (since def f(a: A)(b: B) = ... is not the same as def f(a: A) = {b: B => ...}), but it is not obvious that this should influence the language semantics. Thus, the fact that it’s not working now, can be a bug.

Nevertheless, my message is that implicit functions and implicit function type seem to be somehow helpful in your issue. And maybe Dotty will have enough for you to not to require syntactic changes and SIPs.


#37

Sorry, I do not understand the practical usage of this example.

You are right.
But how will it look on real examples?
Let’s look for examle on ScalaFX or kjdbc

Of course we can use implicit conversions, but this conversions must be imported. It will cause name clashes:

Sorry, but I have been thinking about it for about a year, I just do not know how to make scalable library (like kdbc or scalafx ) with implicits :frowning:


#38

Of course it is possible. We can use prefixes for example :))

But use DelayedInit is much more simple.


#39

there is one more usecase i found for this idea:

//with such simple code
implicit class OptionsSuperOps[T](d:Option[T]) {
  def ?[E] (with this c:T => E) = { d.map(c) }
  //def >[E] (with this c:T => E) = { d.map(c) }
  def ??[E] (with this c:T => Option[E]) = { d.flatMap(c) }
  //def >>[E] (with this c:T => Option[E]) = { d.flatMap(c) }
}



//instead of this
case class Name(first:String, middle:Option[String], last:Option[String])
val name:Option[Name]

User(
  userId = Some(id),
  firstName = name.flatMap(_.first),
  middleName = name.flatMap(_.middle).flatMap(_.headOption),
  lastName = name.flatMap(_.last),
)

//we could write this 
User(
  userId = Some(id),
  firstName = name ? first,
  middleName = name ?? middle ?? headOption,
  lastName = name ?? last,
)

it could be applied to much wider range of classes than Option (especialy using typeclasses ) but let stay with it for simplicity.
We have kotlin’s null checks for free :).

Maybe this is not big deal but still nice consequence of having some kind of scope injection in language.

As i know this cannot be encoded by implicit functions from dotty.

Gain is rather small. In current scala we can do this:

implicit class OptionsSuperOps[T](d:Option[T]) {
  def ?[E] (with this c:T => E) = { d.map(c) }
  def ??[E] (with this c:T => Option[E]) = { d.flatMap(c) }
}

User(
  userId = Some(id),
  firstName = name ? (_.first),
  middleName = name ?? (_.middle),
  lastName = name ?? (_.last),
)

//this won't compile
User(
  userId = Some(id),
  firstName = name ? _.first,
  middleName = name ?? _.middle,
  lastName = name ?? _.last,
)
// |missing parameter type
//  |
//  |The argument types of an anonymous function must be fully known. (SLS 8.5)
//  |Expected type: ?
//  |Missing type for parameter _$2

#40

This does not compile because

  firstName = name ? _.first

means

  firstName = (x => name ? x.first)

i.e. scope of the underscore can be farther than you want. That’s why, bracketless syntax won’t compile ever with the meaning you want.