|
Key
This line was removed.
This word was removed. This word was added.
This line was added.
|
Changes (6)
View Page History{toc}
h3. Brief intro to DSLs
[Fandoc|http://fantom.org/doc/docLang/DSLs.html] says:
[Fandoc|http://fantom.org/doc/docLang/DSLs.html] says:
...
* Modify method bodies
* Remove compilation errors
* Remove compilation errors
But also, we can extend existing types! So, inside DSL plugin we can create new type extending anchor type, and then the instance of our new type will be just upcasted to the base type. On of the examples where it can be useful, is the following:
{noformat}
{noformat}
...
{noformat}
Though, it is not really interesting, let's do something cool, how about...
h3. Actor locals
I've noticed that almost all the time when I use [Actors|http://fantom.org/doc/concurrent/Actor], I write the code like this:
{noformat}
private Str mutableStr
{
get { Actor.locals.getOrAdd("mutableStr") |->Str| { "default value" } }
set { Actor.locals["mutableStr"] = it }
}
{noformat}
It would be great to have a keyword {{actorlocal}}, so I could just write
{noformat}
private actorlocal Str mutableStr := "default value"
{noformat}
But the same effect can be achieved via {{@Magic}}! We can write an {{ActorLocal}} DSL plugin and then use it like this:
{noformat}
@Magic { kind = ActorLocal<||> }
private Str mutableStr := "default value"
{noformat}
Probably a bit more typing than with a keyword, but definitely better than current variant.
So, [ActorLocal|^ActorLocal.fan] DSL does the following:
# If magic facet is applied on type or method, returns compile error
# Removes [Storage|http://fantom.org/doc/compiler/FConst#Storage] flag from the field (to indicate that we provide getter and setter explicitly)
# Removes [Synthetic|http://fantom.org/doc/compiler/FConst#Storage] flag from getter and setter
# If field has the default value, locates initializer (static or instance depending on field flag) and removes field assignment from it (since filed has no storage and we will use initializer in our getter)
# Changes getter and setter to AST equivalents of this code:
{noformat}
get
{
Actor.locals.containsKey("fieldName") ?
Actor.locals["fieldName"] :
(initExpr ?: fieldType.defVal)
}
set { Actor.locals["fieldName"] = it }
{noformat}
Voila!
There's one problem with this code, imagine the case like this:
{noformat}
const class Foo
{
@Magic { kind = ActorLocal<||> }
Str s
}
const class MyActor
{
const Foo foo1
const Foo foo2
...
override Obj? receive(Obj? msg)
{
foo1.s = "str" //now foo2.s equals to "str" too
}
}
{noformat}
And I don't really know if this can be fixed correctly. In theory, we could store map {{\[Foo:FieldType\]}} in {{Actor.locals}}, but there are two obstacles:
# We won't know when to remove values from this map, probably weak keys could help
# Foo must be const - only const objects can be used as map keys
However, I don't think it is a big deal, since typically such fields can be declared in actors itself and used only from {{receive}} method, so they'll act as normal instance fields.
h3. Actor locals
I've noticed that almost all the time when I use [Actors|http://fantom.org/doc/concurrent/Actor], I write the code like this:
{noformat}
private Str mutableStr
{
get { Actor.locals.getOrAdd("mutableStr") |->Str| { "default value" } }
set { Actor.locals["mutableStr"] = it }
}
{noformat}
It would be great to have a keyword {{actorlocal}}, so I could just write
{noformat}
private actorlocal Str mutableStr := "default value"
{noformat}
But the same effect can be achieved via {{@Magic}}! We can write an {{ActorLocal}} DSL plugin and then use it like this:
{noformat}
@Magic { kind = ActorLocal<||> }
private Str mutableStr := "default value"
{noformat}
Probably a bit more typing than with a keyword, but definitely better than current variant.
So, [ActorLocal|^ActorLocal.fan] DSL does the following:
# If magic facet is applied on type or method, returns compile error
# Removes [Storage|http://fantom.org/doc/compiler/FConst#Storage] flag from the field (to indicate that we provide getter and setter explicitly)
# Removes [Synthetic|http://fantom.org/doc/compiler/FConst#Storage] flag from getter and setter
# If field has the default value, locates initializer (static or instance depending on field flag) and removes field assignment from it (since filed has no storage and we will use initializer in our getter)
# Changes getter and setter to AST equivalents of this code:
{noformat}
get
{
Actor.locals.containsKey("fieldName") ?
Actor.locals["fieldName"] :
(initExpr ?: fieldType.defVal)
}
set { Actor.locals["fieldName"] = it }
{noformat}
Voila!
There's one problem with this code, imagine the case like this:
{noformat}
const class Foo
{
@Magic { kind = ActorLocal<||> }
Str s
}
const class MyActor
{
const Foo foo1
const Foo foo2
...
override Obj? receive(Obj? msg)
{
foo1.s = "str" //now foo2.s equals to "str" too
}
}
{noformat}
And I don't really know if this can be fixed correctly. In theory, we could store map {{\[Foo:FieldType\]}} in {{Actor.locals}}, but there are two obstacles:
# We won't know when to remove values from this map, probably weak keys could help
# Foo must be const - only const objects can be used as map keys
However, I don't think it is a big deal, since typically such fields can be declared in actors itself and used only from {{receive}} method, so they'll act as normal instance fields.
h3. Conclusion
The DSL plugins seem to be powerful and dangerous, though quite complex tool, allowing to completely change the meaning of the source code. They strongly rely on Compiler API, which is quite big and likely to be changed. Also there's no clear understanding at which stage the DSL plugins are invoked, and sometimes it may be hard to predict all consequences of AST modifications, so it might be too risky to write something heavy using DSLs.
The DSL plugins seem to be powerful and dangerous, though quite complex tool, allowing to completely change the meaning of the source code. They strongly rely on Compiler API, which is quite big and probably subject to change. Also it is not clear at which stage the DSL plugins are invoked, so sometimes it may be hard to predict all consequences of AST modifications, therefore it might be too risky to write something heavy using DSLs.
On the other hand, in some cases it can provide pretty elegant and robust solutions which can replace a ton of boilerplate code with full support of compile-time validation.
So, it seems to me that DSLs today are sort of black magic, and probably one day it should be revisited and well-organized.