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


#62

IIUC:

val myPage = 'html()(
  'head(),'div()("I wrote this page.")
)

It will be compiled correctly.

It is not problem for json, it is not big problem for html(Html has a very flexible structure).
But for many dsl which have a lot of context dependent grammar.(sql,xml[with xsd],etc)
Static type checking can highly improve usability.

:))))
Sorry, I will not prove you that

  • static type cheking
  • auto code completion

is a very good
:))

If you are in the camp of dynamic schemes you just do not have such problems :wink:


#63

That’s fair, but to @drdozer’s other point – is there anything you need that can’t be done with Scala 3’s implicit function types? I mean, I tend to think of this sort of strongly-typed context-specific syntax as the killer app for that feature…


#64

The DSLs I showed are, intentionally, fairly unstructured. However, it is not difficult to extend these DLS to make them statically type checked, and provide auto completion. It would be relatively trivial to provide an alphabet of HTML tag names taken from e.g. the X-HTML DTD, and constrain that elements can only be nested within others as the DTD allows. The glue is all provided by an implicit that provides the .apply method to use the left-hand-side as if it were a constructor. I didn’t bother adding strong child type constraints because life is short, and these things are best done by code generation from the external type system, but it’s not in principle difficult to do. And you can still render it into a tagless abstraction which retains the ability to separate construction from interpretation.

The difference I think I’m discerning is that my style of building structures is a standard, immutable, referrentially transparent expression. Builder patterns seem to locally construct a mutable object and then set properties on it, before “releasing” it to the containing scope. That is, incidentally, the big difference between my HTML DSL and the one in scalatags.


#65

I disagree with you, I think it is difficult in comparison to kotlin.
https://kotlinlang.org/docs/reference/type-safe-builders.html
And I have not seen any simple example which has comparable flexibility out of the box.


#66

Yes, there is.

I just do not know how it can be simply done with implicit functions.
it is very simple with 5 functions, but when there are 500 -1000 functions it is implicit hell :slight_smile:

Kotlin receiver functions provide simple hierarchical scope management.


#67

Perhaps I’m missing something. The builder pattern appears to work by using a block of statements to set mutable values in the object being built, together with some support for lifting into scope verbs for configuring that object. The tagless DSL approach works by configuring a value through an argument list (rather than block of statements) and the verbs need to be brought into scope by conforming to the argument type(s). I don’t want to get into a holy war about mutability vs expressions. But if the functionality we want to provide is a mechanism to flexibly build complicated, domain-specific expressions with type-safety, then it looks more like an IDE tooling issue to me than a language one. That the IDE can reliably suggest to you what verbs are available within the value building context.


#68

OK, this sounds interesting. Do you have a link so I can take a look? Perhaps looking at a real example, and at a real scale, I’ll see why builder support is required. Cheers :slight_smile:


#69

Sorry, I cannot provide link to our real project it is not open source yet.
There are many dsl in kotlin, but I do not think we need to go far.
Let’s imagine an implemantation of scalafx in

  • implicit function type
  • DeleayedInit(deprecated)
  • kotlin receiver function

IMHO: receiver function will be an etalon of simplicity


#70

OK, What I’m struggling with if we use a block-based notation is this:

html {
  head { title("My awesome page") }
  body {
    head // this is available because we are nested within `html` despite not being directly within it
  }
}

Now, with the dsl based upon application, this doesn’t happen because body.apply(...) doesn’t accept the return value of head.


#71

Ah, interesting point – yeah, I can see that forbidding certain sub-nestings isn’t going to work in the obvious block-based approaches. That’s a good argument for your approach. (Which I generally like, although I wish there was an obvious way to avoid the extra parens.)


#72

I do not think it would be a problem with receiver function, and implicit argument if reciver function were support implicit val shadowing.
Something like:

But It is second task.
The first task is hierarchical scope management which allows increase cohesion.
IMHO: It is just simplify writing, support and using

  • code
  • documentation
  • etc
    by several times
    (at least for my tasks )

#73

Perhaps I’ve written it somewhere else, but IMO scope injection would make tests much better.

Consider this code:

package scope

import java.io.Closeable
import java.nio.file.{Files, Path}

import org.scalatest.{FlatSpec, MustMatchers}

class FixturesDemoSpec extends FlatSpec with MustMatchers {
  behavior of "something"

  it must "do thing 1" in test(1, "a") { fixture =>
    import fixture._
  // test body
  }

  it must "do thing 2" in test(2, "b") { fixture =>
    import fixture._
  // test body
  }

  it must "do thing 3" in test(3, "c") { fixture =>
    import fixture._
  // test body
  }

  class Fixture(val resource: Any, val service: Any, val tempDir: Any)

  def test(arg1: Int, arg2: String)(body: Fixture => Unit): Unit = {
    val resource: Closeable = ???
    val service: Closeable  = ???
    val tempDir: Path       = ???
    try {
      body(new Fixture(resource, service, tempDir))
    } finally {
      resource.close()
      service.close()
      Files.delete(tempDir)
    }
  }
}

Assuming average test body is just a few lines then additional import fixture._ makes code a bit clumsy.

Replacing this:

  it must "do thing 1" in test(1, "a") { fixture =>
    import fixture._
  // test body
  }

with:

  it must "do thing 1" in test(1, "a") { this =>
  // test body
  }

would make tests much more elegant (no repetition) and somewhat more readable.

I don’t see how implicit functions, implicit conversions, etc would replace scope injection without adding a lot of bloat (effectively making that pointless).