How can I generate a main() (access) method for a Haxe class with a constructor call?
For example
static function main() new App()
function new() {
//....
}
and I want to create this with macro, like this:
import haxe.macro.Context;
import haxe.macro.Expr;
class Build {
macro public static function buildFields():Array<Field> {
var fields:Array<Field> = Context.getBuildFields();
var cls = Context.getLocalClass().get();
var pack = cls.pack.concat([cls.name]);
var name = pack.join(".");
fields.push({
name: "main",
access: [Access.APublic, Access.AStatic],
kind: FieldType.FFun({
expr: macro {
Type.createInstance(Type.resolveClass($v{name}), []);
},
args: [],
ret: null
}),
pos: Context.currentPos()
});
return fields;
}
}
#:build(Build.buildFields())
class App {
function new() {
//....
}
}
This generates the main() method fine, but I'm not sure how to generate new App() instead of resorting to Type.createInstance().
To generate a constructor call like new App(), you can reify a haxe.macro.TypePath as documented here.
var typePath:haxe.macro.TypePath = {
pack: cls.pack,
name: cls.name
}
expr: macro new $typePath(),
Btw, instead of manually constructing fields like that, I would suggest using class reification, which lets you use regular Haxe syntax for declaring fields:
fields = fields.concat((macro class {
public static function main() {
new $typePath();
}
}).fields);
Related
Now Solved
This post is a follow up on a previous post
I have taken example code with #:genericbuild which runs ok and modified it, but then it doesn't run.
While I am not new to haxe I am totally new when it comes to macro's. I am used to thoroughly look in documentation and even code if documentation is hard to find. But with haxe macro's that isn't so easy, that's why I hope to find some answers here. With those answers I hope to gain knowledge so I can help improving haxe documentation, so others new to macro's have an easier time.
I have studied documentation available and code.
There are 3 files. The file with driver code (Main.hx), a parameterised enum being generated by a macro(Either.hx) making use of #:genericbuild and the macro file that generates that file (EitherBuildMacro.hx).
I have the latest haxe nightly running. I copied some code of a library that uses genericbuild and runs on my computer. This I will call "reference code" from now on. But when I run the copied and modified above 3 files, I get the error messages:
1) 'Invalid number of type parameters for shl.ds.Either'
2) 'Enum> has no field _1'
It appears the macro can not be executed, also due to errors. It seems the library code which I took as an example doesnt run as an initialization macro, but as a build macro. This is probably the part where I am getting things wrong.
There are also errors about the macrofile (EitherBuildMacro.hx)
1) 'Class has no field defineType'
2) 'Class has no field currentPos'
When I look to the reference code, there all these fields do exist.
Main.hx
class Main{
public static function main(){
var a:Either<String,Int>=Either._1('test'); //<--------
var b:Either<String,Int>=Either._2(123); //<--------
}
}
Either.hx
package shl.ds;
#:genericBuild(shl.macros.build.EitherBuildMacro.build()) //<--------
enum Either<Rest> {} //<--------
EitherBuildMacro.hx
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class EitherBuildMacro {
public static function build() {
for (i in 2...11) {
Context.defineType({ //<--------
pack: [],
name: "Either" + i,
pos: Context.currentPos(), //<--------
kind: TDEnum,
fields: [
for (j in 0...i) {
name: "_" + (j + 1),
kind: FFun({
args: [
{
name: "value",
type: TPath({
name: String.fromCharCode(65 + j),
pack: []
})
}
],
ret: null,
expr: null
}),
pos: Context.currentPos()
}
],
params: [
for (j in 0...i) {
name: String.fromCharCode(65 + j)
}
]
});
}
}
}
The reference code you can find here (https://gist.github.com/nadako/b086569b9fffb759a1b5)
This is the code I took as inspiration
Signal.hx
#:genericBuild(SignalMacro.build())
class Signal<Rest> {} //<--------
SignalMacro.hx
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
class SignalMacro {
static var signalTypes = new Map<Int,Bool>();
static function build():ComplexType {
return switch (Context.getLocalType()) {
case TInst(_.get() => {name: "Signal"}, params):
buildSignalClass(params);
default:
throw false;
}
}
static function buildSignalClass(params:Array<Type>):ComplexType {
var numParams = params.length;
var name = 'Signal$numParams';
if (!signalTypes.exists(numParams))
{
var typeParams:Array<TypeParamDecl> = [];
var superClassFunctionArgs:Array<ComplexType> = [];
var dispatchArgs:Array<FunctionArg> = [];
var listenerCallParams:Array<Expr> = [];
for (i in 0...numParams) {
typeParams.push({name: 'T$i'});
superClassFunctionArgs.push(TPath({name: 'T$i', pack: []}));
dispatchArgs.push({name: 'arg$i', type: TPath({name: 'T$i', pack: []})});
listenerCallParams.push(macro $i{'arg$i'});
}
var pos = Context.currentPos(); //<--------
Context.defineType({ //<--------
pack: [],
name: name,
pos: pos,
params: typeParams,
kind: TDClass({
pack: [],
name: "Signal",
sub: "SignalBase",
params: [TPType(TFunction(superClassFunctionArgs, macro : Void))]
}),
fields: [
{
name: "dispatch",
access: [APublic],
pos: pos,
kind: FFun({
args: dispatchArgs,
ret: macro : Void,
expr: macro {
startDispatch();
var conn = head;
while (conn != null) {
conn.listener($a{listenerCallParams});
if (conn.once)
conn.dispose();
conn = conn.next;
}
endDispatch();
}
})
}
]
});
signalTypes[numParams] = true;
}
return TPath({pack: [], name: name, params: [for (t in params) TPType(t.toComplexType())]});
}
}
And some test driver code
Main.hx
class Main {
static function main() {
var signal = new Signal<Int,String>(); //<--------
var conn = signal.connect(function(a, b) {
trace('Well done $a $b');
});
signal.dispatch(10, "lol");
}
}
I have an ES6 Singleton pattern class with its constructor which has got a variable named name in.
class Sample {
constructor (){
this.name = ''
}
setName = (name)=> {
this.name = name
}
getName = () => {
return this.name
}
}
export default new Sample()
This class is used in another class module with import syntax.
// another class module
import Sample from './Sample'
class AnotherClassModule {
sampleMethod = async () => {
// some code
await Sample.setName('First Name')
}
//some code
anotherSampleMethod = async () => {
// some code
const name = await Sample.getName()
// some code
}
}
As far as I know by the use of this way we have side effect in setName function because it modifies the value of a variable that is out of its scope. Therefore, setName is not a pure function though.
Do we have any solution to change setName to a pure function?
I have the following render method that calls another method in its class:
render() {
const isValidField = this.isValidField();
}
Is it possible - and if so is it a good idea - to use destructuring in this case to avoid repeating the method name in the variable?
I'm looking for something like this:
render() {
const { isValidField }() = this;
}
You could use a getter function:
get isValidField() {
return true;
}
render() {
const {isValidField} = this;
}
I'm adding getter and setters to a variable using haxe macros, now I'm stuck trying to call a static function from within the newly generated setter:
public static function build():Array<Field> {
//.........
// create setter
var setterBody = macro {
$variableRef = v;
// mypackage.MyClass.myFunc(this) <-------- DOES NOT WORK!!
return $variableRef;
};
newFields.push({
pos: Context.currentPos(),
name: "set_" + field.name,
meta: [],
kind: FieldType.FFun({
ret: readType,
params: [],
expr: setterBody,
args: [{
value: null,
type: readType,
opt: false,
name: "v"
}]
}),
doc: "",
access: []
});
In the code above I can't find a way to call MyClass.myFun(this), I don't know how to generate that code for the setter, this refers to the instance of the object where the setter is called.
Thank you very much.
Without a more complete example it's hard to know what went wrong. What I can do is show you code that works:
TiagoLrMacroTest.hx:
#:build( TiagoLrMacro.build() )
class TiagoLrMacroTest {
public static function main() {
var test = new TiagoLrMacroTest();
test.name = "hello";
}
function new() {}
public var name(default,set):String;
}
class MyStaticClass {
public static function staticMethod( a:TiagoLrMacroTest ) {
trace( a.name );
}
}
TiagoLrMacro.hx
import haxe.macro.Expr;
import haxe.macro.Context;
class TiagoLrMacro {
public static function build():Array<Field> {
var fields = Context.getBuildFields();
var setterBody = macro {
name = v;
TiagoLrMacroTest.MyStaticClass.staticMethod( this );
return name;
};
fields.push({
pos: Context.currentPos(),
name: "set_name",
meta: [],
kind: FieldType.FFun({
ret: macro :String,
params: [],
expr: setterBody,
args: [{
value: null,
type: macro :String,
opt: false,
name: "v"
}]
}),
doc: "",
access: []
});
return fields;
}
}
Result (Haxe 3.1.3):
TiagoLrMacroTest.hx:15: hello
The one common gotcha I run into with calling static methods in macros is that imports are not respected, so you have to use full type paths like mypackage.MyClass.myFunc(this), but you are already doing this, so the error must be somewhere else in your code. Happy macro-ing :)
I have a class with over 80 methods, and each method accepts an object containing some defined interface.
class Stuff {
/* many more */
getAccount(req: IAccount, callback: ICallback) {
return this._call('getAccount', req, callback);
}
getIds(req: IIDs, callback: ICallback) {
return this._call('getIds', req, callback);
}
/* many more */
}
pretty 'boring' stuff, since it's just mapping to the underlaying _call method and making it type safe for each of the methods.
But sometimes these req param objects are made up from 2 interfaces or more, and instead of creating another interface for each time there's an "awkward", like this:
export interface ILoled extends IAccount {
loled: boolean;
}
export interface IRofloled extends ILoled {
rofled: boolean;
}
class Stuff {
getLols(req: ILoled){
}
getRofls(req: IRofloled){
}
}
is there any way I can just put it as an "inline" mixin of interfaces inside the method parameter list? like (which obviously don't work):
class Stuff {
getMoreStuff(req: <{} extends IAccount, ITime>) {
}
}
Yes you can, as of Typescript 1.6. Called Intersection types, use the & operator to combine types.
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U> {};
for (let id in first) {
result[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
result[id] = second[id];
}
}
return result;
}
var x = extend({ a: "hello" }, { b: 42 });
x.a; // works
x.b; // works
is there any way I can just put it as an "inline" mixin of interfaces inside the method parameter list
No. You cannot extend an interface inline