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


#42

It looks more like an anti-pattern to me. Introducing a bunch of unchecked mutating stuff, only for making the call-site look slightly more interesting.
You can do virtually the same thing, but safer, with named and default arguments.


#43

Sure, assign a variable directly is a bad idea.
However, assign in Kotlin will call setter function instead of assign directly.


#44

Builder in Dotty


#45

So does Scala if you define a var_= method on a class. This could in theory also work with the automatically generated _= method for vars.
With ThisFunctions, your ajax example could work as follows:

class Ajax() {
  def url_=(url: String): Unit = { /* */ }
  def method_=(url: String): Unit = { /* */ }
  def success(f: String => Unit): Unit = { /* */ }
  def error(f: Exception => Unit): Unit = { /* */ }
}
def ajax(f: this Ajax => Unit): Unit = {
  val aj = new Ajax
  
  f(aj)

  // actually do the ajax evaluation
}

def testHttpOnError(): Unit = {
  val testUrl = "https://www2.baidu.com"

  ajax {
    url = testUrl
    method = "get"
    success { s =>
      println(string)
    }
    error { e =>
      println(e.message)
      Assert.assertTrue("connect timed out" == e.message)
    }
  }
}

with a translation of:

def testHttpOnError(): Unit = {
  val testUrl = "https://www2.baidu.com"

  ajax { _this: Ajax =>
    _this.url_=(testUrl)
    _this.method_=("get")
    _this.success { s =>
      println(string)
    }
    _this.error { e =>
      println(e.message)
      Assert.assertTrue("connect timed out" == e.message)
    }
  }
}

#46

I support this proposal! And think it’s a much better solution than implicit function types, for the kinds of things implicit function types have been advertised for.

Here’s a quick list of other things that implicit function types cannot do:

  • ThisFunction can inject common types like String and Int
    • It is common folk wisdom that in Scala, you of course do not declare common types like Int and String to be implicit, but I think it’s worth pointing out that this is only a limitation of implicits, and not of ThisFunctions/ReceiverFunctions.

E.g.

trait Foo { def x: Int; def y: String } 

val f: ThisFunction[Foo, Int] = {
  x + y.length
}
  • ThisFunction can inject multiple members/values of the same type, unlike implicits

E.g.

case class Foo(x: Int, y: Int, z: Int, zz: Int, zzz: Int) {
  def zzzz: Int
}

val f: ThisFunction[Foo, Int] = {
  x + y + z + zz + zzz + zzzz
}
  • ThisFunction introduces capabilities into a scope in a DISCOVERABLE way. Consider the example typically given, with HTML builders:

class Table {
  val rows = new ArrayBuffer[Row]
  def add(r: Row): Unit = rows += r
  override def toString = rows.mkString("Table(", ", ", ")")
}

class Row {
  val cells = new ArrayBuffer[Cell]
  def add(c: Cell): Unit = cells += c
  override def toString = cells.mkString("Row(", ", ", ")")
}

case class Cell(elem: String)

def table(init: implicit Table => Unit) = {
  implicit val t = new Table
  init
  t
}

def row(init: implicit Row => Unit)(implicit t: Table) = {
  implicit val r = new Row
  init
  t.add(r)
}

def cell(str: String)(implicit r: Row) =
  r.add(new Cell(str))

table {
  row {
    cell("top left")
    cell("top right")
  }
  row {
    cell("bottom left")
    cell("bottom right")
  }
}

This example is taken from the Dotty website. However even this example is much better handled by ThisFunction’s. The problem with implicits here is that we are relying on the availability/unavailablility of many implicit values to tell us what is “in scope / available to use” and what isn’t. For example:


cell("top left")
// ^-- only because implicit search fails
//  do I find out this is wrong! `cell` is in scope, but
//  I would have to simulate implicit search to know that it 
//  can't be used here

row {
}
// ^-- same thing here

table {
  cell("top left")
  // ^-- and here
  row {
    cell("top left")
    cell("top right")
  }
  row {
    cell("bottom left")
    cell("bottom right")
  }
}

In Kotlin, or with ThisFunction, it is extremely obvious what is and what is not available.

  • With Implicit function types, even with all this working you still need to know where is the big import you need to perform. You’ll have to search around for where are all these little DSL functions in some package object somewhere and remember to import com.example.my.package.dsl._ whereas with ThisFunction, the exact right members are imported into scope in exactly the right places and only in the right places.

An other benefit: ThisFunction does not imply (and therefor commit to) any kind of ordering to the members injected. Suppose I have the following equivalent code under both ThisFunction and Implicit-Function-Types styles

trait Foo { def x: String; def y: Int }

val f: ThisFunction[Foo, Unit] = {
  print(x + y)
}
f(new Foo("", 1))

val g: (implicit x: String, y: Int) => Unit = {
  print(x + y)
}

g("", 1) // calling g explicitly

Now imagine I change the order around…

trait Foo { def y: Int; def x: String }

val g: (implicit y: Int, x: String) => Unit = {
  print(x + y)
}

^ here, the order of the parameters changing has broken the call to g, but f is perfectly fine.

@scalway thank you for this proposal! I really hope something like this can get into Scala. :tada:


#47

After recently merged dotty extension methods pull request It become available to “mimic” something similar to what is expected by the means of specially arranged extension methods.
Actually I’ve already mentioned about this possibility in some previous post but that example is little bit obsolete (uses previous syntax), and probably not enough expressive.

So here is rewritten version of that crafts, which actually does something similar to
builder.scala
but using extension methods for some sort of scope semi-injection (here I call it “semi-injection” because “injected” names becomes available not “as is” but with some %-prefix)

// this is currently compilable/working code - works under current state of dotty master branch

object TestMain {

  def main(args: Array[String]): Unit = { Test.testDsl() }


  object Test {
    import builders.table
    import dslActivators.{dsl => %}

    val data =
      %table {
        %row {
          %cell("A1")
          %cell("B1")
        }
        %row {
          %cell("A2")
          %cell("B2")
        }
      }

    def testDsl(): Unit = {
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }
  }


  import scala.collection.mutable.ArrayBuffer

  object dslActivators {

    // dsl activating marker
    trait DslActivator

    // any "dummy" name that should serve as extension target (part of scope semi-injection)
    object dsl extends DslActivator

  }

  object builders {
    import models._
    import dslActivators._

    def (erased dslActivator: DslActivator) table (tableInit: implicit TableBuilder => Unit) : Table = {
      val tableBuilder = new TableBuilder
      tableInit(tableBuilder)
      tableBuilder.build
    }

    class TableBuilder {
      val target = new Table()
      def build: Table = target

      def (erased dslActivator: DslActivator) row (rowInit: implicit RowBuilder => Unit) : this.type = {
        val rowBuilder = new RowBuilder
        rowInit(rowBuilder)
        target.add(rowBuilder.build)
        this
      }
    }

    class RowBuilder {
      val target = new Row()
      def build: Row = target

      def (erased dslActivator: DslActivator) cell (payload: String) : this.type = {
        target.add(new Cell(payload))
        this
      }
    }
  }


  object models {
    class Table {
      val rows = new ArrayBuffer[Row]
      def add(r: Row): Unit = rows += r
      override def toString = rows.mkString("Table(", ", ", ")")
    }

    class Row {
      val cells = new ArrayBuffer[Cell]
      def add(c: Cell): Unit = cells += c
      override def toString = cells.mkString("Row(", ", ", ")")
    }

    class Cell(elem: String) {
      override def toString = s"Cell($elem)"
    }
  }

}

Here one can see that

    val data =
      %table {
        %row {
          %cell("A1")
          %cell("B1")
        }
        %row {
          %cell("A2")
          %cell("B2")
        }
      }

Is in fact

    val data =
      %.table {
        %.row {
          %.cell("A1")
          %.cell("B1")
        }
        %.row {
          %.cell("A2")
          %.cell("B2")
        }
      }

In “global” scope imported only builders.table (which is extension method applicable to %), other names from this snippet (%.row , %.cell) are not globally imported, instead they are “inject” into scope in some “fine way” by implicit functions.

So that this approach could be formally treated as some sort of workaround for issue highlighted by @AMatveev in his post related to DelayedInit vs implicit functions types

Continuing writing some other variation of aforementioned snippet one can also write something like

// this is currently compilable/working code - works under current state of dotty master branch

object TestMain1 {

  def main(args: Array[String]): Unit = {
    Test.testDsl()
    Test2.testDsl()
    Test3.testDsl()
  }


  object Test {
    import builders.table
    import dslActivators.dsl

    val data =
      %table {
        %row {
          %cell("A1")
          %cell("B1")
        }
        %row {
          %cell("A2")
          %cell("B2")
        }
      }

    def testDsl(): Unit = {
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }
  }

  object Test2 {
    import builders.table
    import dslActivators.dsl

    val data =
      dsl.table {
        dsl.row {
          dsl.cell("A1")
          dsl.cell("B1")
        }
        dsl.row {
          dsl.cell("A2")
          dsl.cell("B2")
        }
      }

    def testDsl(): Unit = {
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }
  }

  object Test3 extends dslActivators.DslActivator {
    import builders.table

    def testDsl(): Unit = {
      val data =
        Test3.this.table {
          Test3.this.row {
            Test3.this.cell("A1")
            Test3.this.cell("B1")
          }
          Test3.this.row {
            Test3.this.cell("A2")
            Test3.this.cell("B2")
          }
        }
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }
  }


  import scala.collection.mutable.ArrayBuffer

  object dslActivators {

    // dsl activating marker
    trait DslActivator

    // any "dummy" name that should serve as extension target (part of scope semi-injection)
    object dsl extends DslActivator

  }

  object builders {
    import models._
    import dslActivators._

    def (erased dslActivator: DslActivator) table (tableInit: implicit TableBuilder => Unit) : Table = {
      val tableBuilder = new TableBuilder
      tableInit(tableBuilder)
      tableBuilder.build
    }

    class TableBuilder {
      val target = new Table()
      def build: Table = target

      def (erased dslActivator: DslActivator) row (rowInit: implicit RowBuilder => Unit) : this.type = {
        val rowBuilder = new RowBuilder
        rowInit(rowBuilder)
        target.add(rowBuilder.build)
        this
      }
    }

    class RowBuilder {
      val target = new Row()
      def build: Row = target

      def (erased dslActivator: DslActivator) cell (payload: String) : this.type = {
        target.add(new Cell(payload))
        this
      }
    }
  }


  object models {
    class Table {
      val rows = new ArrayBuffer[Row]
      def add(r: Row): Unit = rows += r
      override def toString = rows.mkString("Table(", ", ", ")")
    }

    class Row {
      val cells = new ArrayBuffer[Cell]
      def add(c: Cell): Unit = cells += c
      override def toString = cells.mkString("Row(", ", ", ")")
    }

    class Cell(elem: String) {
      override def toString = s"Cell($elem)"
    }
  }

}

Here most remarkable looks that snippet

    def testDsl(): Unit = {
      val data =
        Test3.this.table {
          Test3.this.row {
            Test3.this.cell("A1")
            Test3.this.cell("B1")
          }
          Test3.this.row {
            Test3.this.cell("A2")
            Test3.this.cell("B2")
          }
        }
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }

If that issue related to this.extMethod() <==> extMethod() natural behaviour
would be fixed, then that snippet would become look like “true scope injection”

// this is not valid code in current state of dotty master branch since `this.extMethods()` != `extMethods()` for now

  object Test3 extends dslActivators.DslActivator {
    import builders.table

    def testDsl(): Unit = {
      val data =
        table {
          row {
            cell("A1")
            cell("B1")
          }
          row {
            cell("A2")
            cell("B2")
          }
        }
      println(data)
      assert(s"$data" == "Table(Row(Cell(A1), Cell(B1)), Row(Cell(A2), Cell(B2)))")
    }
  }

But any way it could not be treated as final solution, rather as some tricky workaround.
Something similar to Kotlin’s extension methods / extension functions / extension lambadas
may fit for this problem much better.


#48

The link to

is this Extension Method


#49

Thanks, yes, exactly that pull request (link fixed)


#50

It is a pitty, but I think it will not help me much :frowning:

  • I would prefer to write html.table instead of %table because in such cases an operator has a magic sense. In our company it is bad style when you need look up headers to understand code. Such contraction can be used only in local code block.
  • The second greatest disadvantage of “implicit” in comparison to “receiver function” is absence of context dependent grammar . So I would afraid to use such approach in complex library like scalaFx I would prefer to use DelayedInit.

#51

Related links



#52

By the way, I don’t know if this has been proposed here before, but you can use Dotty’s new keyword without having to specify the type when it can be inferred. This can make the kind of DSLs you want to design much more palatable:

head(new {
	someFunctionOnX() 
	other() 
})

You just needs these definitions:

inline def head(mk: => A): A {
  // do what you want before
  val res = mk // construct the object, which executes its constructor
  // do what you want after
  res
}
class A {
  def someFunctionOnX() = ...
  def other() = ...
}

Since head is inline, Dotty could probably easily remove the creation of the intermediate object.

Frankly, since the kind of syntax you want can be achieve with just the addition of a 3-character keyword and an additional pair of braces at the call sites, I think nothing would justify a language change like the one propose here.

I actually think having this new right there in the middle is a good idea, for code readability. Scala already has too much magic going on with its name resolution mechanism. Being able to see where injected scopes begin and end is a good thing IMHO.


#53

Actually this example is not completely correct.
see

Kotlin reciever function do not use constructors for building. It is important thing.


#54

I don’t see the problem. This use-case should be handled fine. Notice that I made the parameter to head by-name, which means that we can control the execution of the object creation.

Kotlin reciever function do not use constructors for building. It is important thing.

If you want to have access to an existing object, you could just make users new a proxy to that object:

class A { a =>
  def someFunctionOnX() = ...
  def other() = ...
  class Proxy {
    def someFunctionOnX() = a.someFunctionOnX()
    def other() = a.other()
  }
}
inline def head(a: A)(code: => a.Proxy): Unit = {
  code // execute the user's scope-injected code
}

val a = new A
head(a)(new{
  println(someFunctionOnX()) // scope injection!
})

#55

I’ve seen this after this PRESIP was made. Dotty has few features that’ll somehow tackle this problem. (new {...}) works but still is rather ugly. Implicit functions gives us similar freedom with less noise but implementation’ll be much more tricky.


#56

I think you are very smart person(it is a complement, it is not joke).

I am sure you can make html dsl easily and you would not meet any problem.

But unfortunately there are more usual people.
I am sure if I would give him kotlin’s html dsl and your html dsl.
They would say that your version is whatever you want but it is not simplicity.

I need grammar as for smart people as for more usual.
I know that somebody think that ide is for people who are too lazy.
But I need the library for such people. I need that code assistant can support such library more better.
I need the library which can be supported by more usual people.

I do not know I want to save money or make the world a bit better :)))

IMHO I am sure that at least I will save money if such proposal is implemented :wink:
note.
It does not prove the need of this proposal.


#57

You are right. If they solve the problem of name clashes. We can use implicit functions for example.

//decorate for init method call 
html(new{ //automate scope injection
   body{
       table{
       }
   }
})

But as I have said the code assistance will be awful.
The library will be very complicated, because of the global variables.
For example our dsl markup language has over 100 classes and over 500 properties between this classes.
When I think about it in a global list I feel some toothache :slight_smile:
We use pure xml now and I am not sure that dsl with implicits will have a sense.But we have java pojo classes and it is usefull.

If we make dsl for such markup one day I am not sure that usual people can support its code and documentation.


#58

A use case will be type checking inside a context

for example

type Context

def (this: Context) infer(term: Term) = {
  term match {
    case Term.Pi(domain, codomain) =>
      extend(assertIsTypeAndEval(domain)).run {
         infer(codomain)
         // other stuff in the new context
      }
  }
}

#59

Sorry to be that annoying person, but I’m unsure what the problem is that this addresses. Using out-of-the-box scala 2 features, I have fluent DSLs for HTML and JSON that ‘just work’ and have representational independence.

val myPage = 'html()(
  'head()(
    'title()("My page")),
  'body('class := "myStyle")(
    'h2('id := "ex_123")("This is my awesome page"),
    'div()("I wrote this page.")
  )
)

val js = jObject {
  'name := "Matthew",
  'food := "Fish" :: "Chips" :: Nil,
}

This uses an underlying tagless DSL for HTML and Json, respectively, and some syntactic flourishes to provide a := assignment syntax and to coerce native types into the DSL types. So you can interpret an HTML expression to, e.g., strings, or DOM, or harvest all IDs, or to validate classes. Similarly, the JSON dsl can be evaluated to JSON strings, to an ultra-light in-memory DOM, to first-cuts at type dictionaries from values, and so-forth. Now I will grant that there is some machinery involved in achieving this. Scala 3 makes this machinery much nicer. But I don’t see what a dedicated builder extension adds that is not met by using an appropriate tagless encoding with a suitable layer of sugar over the top. What does a builder extension add?


#60

FYI symbol literals have been / are being dropped https://github.com/scala/scala-dev/issues/459


#61

Yeah - I ran into that when I started porting this to scala 3 over a beer one night. The updated version allows string literals in place of the symbol literals.