name of feature
this functions
, dsl function
, context functions
, receiver function
or import function
don’t know which name is better. In whole document i’m using this function
consequently.
specification
I propose to introduce additional trait into scala
trait ThisFunction[-T, +R] extends Function1[T, R]
//trait ReceiverFunction[-T, +R] extends Function1[T, R]
//trait ContextFunction[-T, +R] extends Function1[T, R]
//trait ImportFunction[-T, +R] extends Function1[T, R]
//trait DslFunction[-T, +R] extends Function1[T, R]
//trait ScopeFunction[-T, +R] extends Function1[T, R]
//trait Scope[-T, +R] extends Function1[T, R] //???
it is like normal Function1
and always has only one argument.
It is specially treated when such function is defined literally:
//val fun:Scope[SomeBuilder, Any] = {
//val fun:ReceiverFunction[SomeBuilder, Any] = {
val fun:ThisFunction[SomeBuilder, Any] = {
someFunctionOnX()
other()
}
It is defined not as function but as block (as seen above).
Inside definition of this function we see all fields, methods, implicits and extension methods of SomeBuilder without any prefix.
that can be used for example like this (simplest possible use-case):
def head(f:ThisFunction[SomeBuilder, Any]) = ???
which can be replaced by simplified notation:
def head(this f:SomeBuilder => Any]) = ???
//or
def head(f: this SomeBuilder => Any]) = ???
//if `this` keyword is unacceptable, then we can use other keywords instead
//def head(import f:SomeBuilder => Any]) = ???
//def head(with f:SomeBuilder => Any]) = ???
//def head(with import f:SomeBuilder => Any]) = ???
it’ll allow to call it like this. It is similar to kotlin receiver function.
head {
someFunctionOnX()
other()
}
It is ‘almost’ equivalent to such code
head { x =>
import x._
someFunctionOnX()
other()
}
‘almost’ means that there are some diffrences. Here are rules that are applied into call site:
- we can use all fields/methods that are in SomeBuilder
- all implicit fields/methods are avaliable in this scope without prefix
- we can use extension methods of SomeBuilder in place (insted of
x.extFunc()
we can writeextFunc()
) - we don’t do anything with
this
reference in block. It shows to whatever it shown previously (place where ThisFunction is bit confusing)! - instance of SomeBuilder is not accessible (it has no name). Below is proposition how to obtain this reference.
head
is ordinary method and it should be possible to just call it like other methods. We need to remember that on call site it’ll be seen as it expect Any! Kind of head’s first argument is changed from expected (*) -> (*) to (*)
head { println("yea") }
head { 22 }
Such code should be disallowed due to wrong kind that cannot be redused properly:
def head(this f:SomeBuilder) = ??? //compilation error
def head(this f:(SomeBuilder, String) => Any]) = ??? //compilation error
example with expected function
//all definitions are exactly the same
def head(f:ThisFunction[SomeBuilder, Function1[Key, Any]]) = ???
def head(f:ThisFunction[SomeBuilder, Key => Any]) = ???
def head(this f:SomeBuilder => Key => Any]) = ???
head { k =>
someFunctionOnX(k)
other(k)
}
/** or even */
head { implicit k =>
someFunctionOnX()
other()
}
Motivation
Scala has lot of features that simplify to write dsls, but still lacks of good feature that’ll introduce context precisely. We can ‘imit’ this feature using new
in our dsl but there are cases where it is not desirable.
new Head { /** we have access to every function in head ;) */}
interactions with ImplicitFunction
important part is how it’ll behave with ImplicitFunctions?
def head(f:ThisFunction[SomeBuilder, ImplicitFunction1[Key, Any]]) = ???
//this is extreamly powerfull. Rather not happy with that!
def head(this f:SomeBuilder => implicit String => Any]) = ???
//same as above
//this'll allow for such constructions?
//Much to POWERFULL!
head {
val x = implicitly[Key] //comes from ImplicitFunction1 ???
someFunctionOnX(x) //comes from SomeBuilder.
}
define function in other place than call site
val func: ThisFunction[SomeBuilder, Any] = {
someFunctionOnX()
other()
}
head(func)
using Function1 as ThisFunction
we should be able to pass normal function to head.
val func = f:SomeBuilder => ()
head(func)
but then types does not fit. Theoretically we can fix that by simple implicit conversion from Function1[SomeBuilder, Any]
to ThisFunction[SomeBuilder, Any]
, but it can be to much fragile (TODO test how much).
Not sure if it should be done by implicitly, but we can do it explicitly using extension method on Function1
val func = f:SomeBuilder => ()
head(func.toThis)
//head(func.toThisFunction)
//head(func.toContextFunction)
//head(func.asThis)
//head(func.this)
use this argument explicitly
Ok, but sometimes we want to explicitly name this
argument. What then?
we can use special notation (mark argument as this) and compiler should be happy:
head { this x =>
x.someFunctionOnX()
x.other()
}
val func:ThisFunction[SomeBuilder, Any] = this x => {
x.someFunctionOnX()
other() //still possible
}
head(func)
//TODO what with such construct?
val func:ThisFunction[SomeBuilder, Any] = implicit this x => {
x.someFunctionOnX()
other() //still possible
}
what heappen when we miss this keyword?
head { x =>
x.someFunctionOnX()
x.other()
}
Thankfully compiler should show us error here that he doesn’t know type of x. It expects Any and cannot infer type of argument of annynous function, byt if we’ll be bit more helpfull here:
head { x:SomeBuilder =>
x.someFunctionOnX()
x.other()
}
then this code’ll compile (and it’ll be horribly wrong!). This should never happen if we’ll accept more useful result type than Any. Still i think this is the weakest part of whole PRE_SIP