Macro to fill out field names, values, and switch statements of a custom #:enum abstract? - macros

I have a bit of a unique situation. For various reasons, chiefly interoperating with a nullable stringly typed legacy system, as well as various other needs I won't go into at the moment, I've settled on a custom #:enum abstract that looks like this:
#:enum abstract MyEnum(Null<Int>) {
public var A = 0;
public var B = 1;
public var C = 2;
public var D = 3;
public var E = 4;
public var F = 5;
public var G = 6;
#:from private static function fromString (value:String):MyEnum {
return switch (value) {
case "a": A;
case "b": B;
case "c": C;
case "d": D;
case "e": E;
case "f": F;
case "g": G;
default: null;
}
}
#:to private static function toString (value:Int):String {
return switch (value) {
case A: "a";
case B: "b";
case C: "c";
case D: "d";
case E: "e";
case F: "f";
case G: "g";
default: null;
}
}
}
However, that's an annoyingly large amount of things to type, and when adding and removing members it's easy to make a manual error. Clearly, this follows a super predictable pattern and seems like a great thing to construct with a macro, but I am terrible at haxe macros.
Can someone explain how I could use a macro to build this enum in such a way that all I have to supply is a list of field names?
pseudocode:
#:enum abstract MyEnum = doTheMacroMagic(["A","B","C","D","E","F","G"]);
The logical steps would be:
Declare public vars from field names (upper case)
Declare fromString/toString values from field names (lower case)
Set public vars to 0-based integers and follow same order as field names are supplied
I think a simple practical example like this might make haxe macros finally "click" for me if I can see it in action.

Flixel handles a very similar use case in for classes like FlxKey with FlxMacroUtil.buildMap(). This expression macro looks for all uppercase, inline vars it finds in the abstract and generates a Map<String, EnumType> from it, with the keys being the field names and the values the field values (or the inverse of that if invert is true).
#:enum
abstract FlxKey(Int) from Int to Int
{
public static var fromStringMap(default, null):Map<String, FlxKey>
= FlxMacroUtil.buildMap("flixel.input.keyboard.FlxKey");
public static var toStringMap(default, null):Map<FlxKey, String>
= FlxMacroUtil.buildMap("flixel.input.keyboard.FlxKey", true);
var A = 65;
var B = 66;
// more keys...
#:from
public static inline function fromString(s:String)
{
s = s.toUpperCase();
return fromStringMap.exists(s) ? fromStringMap.get(s) : NONE;
}
#:to
public inline function toString():String
{
return toStringMap.get(this);
}
}
I'd imagine that's a good starting point. If you want to generate the entire abstract, you will need a #:build macro.
Answering the follow-up question, how to generate fields: This is actually quite straightforward with a build macro:
#:enum
#:build(Macro.createVariables(["A", "B", "C", "D", "E"]))
abstract Generated(Int)
{
}
Macro.hx (sensible to have in its own file to avoid having to deal with #if macro conditionals):
package;
import haxe.macro.Context;
import haxe.macro.Expr;
class Macro
{
public static macro function createVariables(varNames:Array<String>):Array<Field>
{
// get the current fields of the calling type (empty array in this case)
var fields = Context.getBuildFields();
for (i in 0...varNames.length)
// create a custom variable and add it to the fields
fields.push(createVariable(varNames[i], i));
return fields;
}
private static function createVariable(name:String, value:Int):Field
{
return {
name: name,
doc: null,
meta: [],
access: [Access.APublic, Access.AStatic, Access.AInline],
kind: FieldType.FVar(macro:Int, macro $v{value}),
pos: Context.currentPos()
}
}
}
You'll notice the fields showing up in auto-completion for Generated.. You can also see what's been generated by looking at the Generated_Impl.dump when doing an AST dump.

Related

Can a Swift enum be referenced as a variable?

I am centralizing all my application strings in enums, and these strings are all namespaced to the application feature in which they are used (example below).
When I attempt to store the enum in a variable (like var strings = Strings.Feature.SubFeature) and call it like strings.someStringValue, I get a Expected member name or constructor call after name type error.
Declaration:
enum Strings {
enum Feature {
enum Subfeature {
static var someString: String { "some string".localizedLowerCase }
}
}
}
Callsite:
someLabel.text = Strings.Feature.Subfeature.string
Hoped-for behavior:
var strings = Strings.Feature.Subfeature
someLabel.text = strings.someString
Is it possible to store a reference to the containing enum, such that I will not have to reference the full path every time I use a given string? I would love to know if there are any alternative ways of going about this as well.
Joakim's answer looks like it answers your question, but another option (with potentially lower memory use?) would be using a typealias.
typealias SubFeatureStrings = Strings.Feature.Subfeature
and then
SubFeatureStrings.someString
The typealias could be nested inside the class/struct where you're calling it, to avoid conflicts across your app.
enum Strings {
enum Feature {
enum Subfeature {
static var someString: String { "some string".localizedLowerCase }
static var otherString: String { "some other string".localizedLowerCase }
}
}
}
var strings = Strings.Feature.Subfeature.self
someLabel.text = strings.someString
someLabel.text = strings.someOtherString
An enum can be enum <#name#> : String
enum Foo : String {
case bar = "bar"
}
usage: <#something#>.text = Foo.bar.rawValue
For chained purposes
enum SomeEnum {
enum Foo : String {
case bar = "bar"
}
}
usage: <#something#>.text = SomeEnum.Foo.bar.rawValue
Then you can typealias the chain:
typealias foo = SomeEnum.Foo
<#something#>.text = foo.bar.rawValue

Under which conditions I should prefer computed properties over stored properties?

I saw this piece of code today, and was wondering why you would not instead use simple static stored properties?
This is the code that I am curious about:
class ApiKeys {
// movie keys
class var HomePage: String { get { return "homepage" } }
class var Id: String { get { return "id" } }
class var Overview: String { get { return "overview" } }
class var PosterPath: String { get { return "poster_path" } }
class var ReleaseDate: String { get { return "release_date" } }
class var Runtime: String { get { return "runtime" } }
class var Tagline: String { get { return "tagline" } }
class var Title: String { get { return "title" } }
class var Rating: String { get { return "vote_average" } }
// query params
class var ApiKey: String { get { return "api_key" } }
class var Query: String { get { return "query" } }
}
And this is how I would have written the same code:
class ApiKeys {
static let homePage = "homepage"
static let id = "id"
static let overview = "overview"
static let posterPath = "poster_path"
static let releaseDate = "release_date"
static let runtime = "runtime"
static let tagline = "tagline"
static let title = "title"
static let rating = "vote_average"
//Query Params
static let ApiKey = "api_key"
static let query = "query"
}
There won't ever be any need to override the variables, so use of static should be okay. Am I missing something? Is there any advantage or reason to use the first method over the second?
For what it's worth, I wouldn't be inclined to use computed or stored properties at all. Rather than defining this to be a class, this seems like a textbook case for an enum:
enum ApiKey: String {
// movie keys
case HomePage = "homepage"
case Id = "id"
case Overview = "overview"
case PosterPath = "poster_path"
case ReleaseDate = "release_date"
case Runtime = "runtime"
case Tagline = "tagline"
case Title = "title"
case Rating = "vote_average"
// query params
case ApiKey = "api_key"
case Query = "query"
}
This more accurately captures the notion that a "key" can be one of those values.
And you'd use it like so:
if key == ApiKey.HomePage.rawValue {
...
}
Or
if ApiKey(rawValue: key) == .HomePage {
...
}
In answer to your original question, “when should I prefer computed properties”, the answer is that you generally use them to retrieve a value computed from other properties and, optionally, if you want to set other (possibly private) properties and values indirectly. There's little benefit to using computed properties if you're just going to return some static, unchanging string.
A class var can be overridden by a subclass while a static constant can't. That's the first difference I can think about.
Computed properties can be used to dynamically change the value of the property at runtime if necessary, just like and overridden getter can in Objective-C. You can't do that with a static let constant.
Possibly somewhat off-topic: but one possibly contrived usage scenario where static stored properties cannot be used is if you define non-blueprinted static computed properties with default implementations in an extension to some "constants" protocol. Classes/structs/etc that conform to such a protocol can be allowed to access type constrained generics, where these generics are the the only context in which the protocol constants are accessible (limit the accessibility to the constants) where they are guaranteed to be constants (since they can also be used directly from the concrete types that conform that protocol, but these can "override" the "constants" with new values).
protocol HasAccessToConstants {
/* since we don't blueprint 'theAnswer', the default
implementation below will always be used for objects
conforming to this protocol when used in a generic
context (even if they attempt to "override" these
"constants" with implementations of their own, these
custom ones can only be accessed for concrete-types). */
}
extension HasAccessToConstants {
static var theAnswer: Int { return 42 }
/* for protocols: we may implement a default
implementation only for computed properties */
}
class Foo : HasAccessToConstants {
/* Even if the developer implements its own "constant"
implementation, this will not be used for accessing
Foo type in a generic context. */
static var theAnswer: Int { return 9 }
}
func onlyForObjectsWithAccessToConstants<T: HasAccessToConstants>(obj: T) {
// do something with obj ...
// make use of constants available to the type of obj
print("Constants available to the type of this object (e.g. '\(T.theAnswer)')")
}
onlyForObjectsWithAccessToConstants(Foo())
/* Constants available to the type of this object (e.g. '42') */
// not really "constants" as they can be "overridden" for concrete types
print(Foo.theAnswer) // 9 (since concrete type)
Again, contrived, and included for the technical discussion, as I can't really see in what scenario this would be more useful than other, better alternatives.

What is the best way to get Haxe function parameter types using a macro?

I want to get the parameter types of a Haxe function using a macro and convert them to a shorthand string form, a bit like JNI/Java method signatures, but without a return type.
The motivation here is to provide access to the function parameter types, without having to slowly search through run-time type information at runtime. For example, say you want to construct a graphical widget for calling a function that takes parameters. You will need the type of each function parameter to create the correct spinbox, textbox, and select box widgets needed for tweaking the values that will be passed to the function.
So the question is, how can you save Haxe function parameter types with a macro?
Here is a macro that works for a few basic types, and any abstracts based on those types. It maps the function parameter types to strings. For example, function type String->Float->Int->String->Void maps to sfis, Float->Float->Int to ff etc:
package;
import haxe.macro.Expr;
import haxe.macro.Context;
import haxe.macro.Type;
import haxe.macro.ExprTools;
// Map some Haxe types to string ids
#:enum abstract TypeMapping(String) from (String) {
var BOOL = "b";
var FLOAT = "f";
var INT = "i";
var STRING = "s";
}
class Util
{
public macro static function getParameterTypes(f:Expr):ExprOf<String> {
var type:Type = Context.typeof(f);
if (!Reflect.hasField(type, 'args')) {
throw "Parameter has no field 'args'";
}
var t = type.getParameters()[0];
var args:Array<Dynamic> = Reflect.field(type, 'args')[0];
var signature:String = "";
for (i in 0...args.length) {
switch(args[i].t) {
case TAbstract(t, p):
var underlyingTypeName = Std.string(t.get().type.getParameters()[0]);
switch(underlyingTypeName) {
case "Bool":
signature += TypeMapping.BOOL;
case "Float":
signature += TypeMapping.FLOAT;
case "Int":
signature += TypeMapping.INT;
case "String":
signature += TypeMapping.STRING;
default:
throw "Unhandled abstract function parameter type: " + underlyingTypeName;
}
case CString:
signature += TypeMapping.STRING;
default:
throw "Unhandled function parameter type: " + args[i];
}
}
return macro $v{signature};
}
}
A further problem is how to make this work for all types, rather than just ones you handle explicitly. To do that, you might populate an array of Strings with the type name/class name/path of each function parameter instead, and return that instead of a single String. Here's an attempt at that, note it doesn't work with function parameters (and probably other stuff) yet:
public macro static function getFullParameterTypes(f:Expr):ExprOf<Array<String>> {
var type:Type = Context.typeof(f);
if (!Reflect.hasField(type, 'args')) {
throw "Parameter has no field 'args'";
}
var args:Array<Dynamic> = Reflect.field(type, 'args')[0];
var pos = haxe.macro.Context.currentPos();
var signature:Array<Expr> = [];
for (i in 0...args.length) {
var argType:Type = args[i].t;
var s;
switch(argType) {
case TFun(t, r):
s = EConst(CString("Function"));
throw "Not working with function parameters yet";
case _:
s = EConst(CString(argType.getParameters()[0].toString()));
}
signature.push({expr: s, pos: pos});
}
return macro $a{signature};
}
A more up to date approach..
macro function deflate(fun:haxe.macro.Expr) {
var type = haxe.macro.Context.typeof(fun);
final paramNames = extractFunction(type);
return macro $v{paramNames};
}
// Extract function parameter names
function extractFunction(type):Array<Dynamic> {
return switch type {
case TFun(args, ret): {
var paramNames:Array<Dynamic> = [];
for (p in args) {
final pName = p.name;
paramNames.push(pName);
}
return paramNames;
}
case _: {throw "unable to extract function information";};
}
}
Use it like this
using Macros;
function func(name:String, greeting:String){};
final args = fun.deflate();
trace(args) // output: [name, greeting]
A problem you may face is how to collect the default value of a parameter, consider the example below.
function func(name:String = "Josh", greeting:String = "Hello"){ return '$greeting $name'};
final args = fun.deflate();
trace(args) // output: [name, greeting]
Now let's account for default parameter values by slightly modifying the code:
// Extract function parameter names
function extractFunction(type):Array<Dynamic> {
return switch type {
case TFun(args, ret): {
var paramNames:Array<Dynamic> = [];
for (p in args) {
final pName = p.name;
final v = {name: pName, value: null}; // <= anticipate a value
paramNames.push(v);
}
return paramNames;
}
case _: {throw "unable to extract function information";};
}
}
macro function deflate(fun:haxe.macro.Expr) {
var type = haxe.macro.Context.typeof(fun);
final paramNames:Array<Dynamic> = extractFunction(type);
// extract default param values
switch fun.expr {
case EFunction(f, m):{
for(a in m.args){
for(p in paramNames){
if(p.name == a.name){
if(a.value != null){
switch (a.value.expr){
case EConst(c):{
switch(c){
case CString(v, _):{
p.value = v;
}
case CFloat(f): {
p.value = Std.parseFloat(f);
}
case CInt(i):{
p.value = Std.parseInt(i);
}
case _: throw "unsupported constant value for default parameter";
}
}
case _:
}
}
}
}
}
}
case _:
}
return macro $v{paramNames};
}
So we can now use it like this
function func(name:String = "Josh", greeting:String = "Hello"){ return '$greeting $name'};
final args = Macros.deflate(func);
trace(args) // output: [{name: 'name', value:'Josh', {name:'greeting', value:'Hello'}]

Selecting class depending on variable

I have two classes ClassOne and ClassTwo. I want to initialize a different one depending on a variable value, i want to do something like:
if(a == "0") {
let b = ClassOne();
}else{
let b = ClassTwo();
}
without having to write it everytime I need it. Something like:
let b = MainClass()
and gets called ClassOne() or ClassTwo() depending on the case, a is a global variable.
In order for this to work the two types should be related either by extending a common base class or by implementing the same protocol. Further, subsequent operations on b would be restricted to these the two classes have in common.
If you are fine with that restriction, you can do it like this:
protocol CommonProtocol {
func foo() -> Double
var bar : Int { get }
}
class ClassOne : CommonProtocol {
...
}
class ClassTwo : CommonProtocol {
...
}
func MainClass() -> CommonProtocol {
if(a == "0") {
return ClassOne()
} else {
return ClassTwo()
}
}
...
let b = MainClass()
b.foo()
print(b.bar)
Note: You could forego all of the above in favor of a completely dynamic approach by following matt's advise.
You can do it, but it isn't going to be useful without further effort. Consider the following:
class ClassOne {}
class ClassTwo {}
Now we proceed to initialize a variable as an instance of one of these classes. To do so, we must type the variable as AnyObject:
var which : Bool { return true /* or false */}
let obj : AnyObject
switch which {
case true:
obj = ClassOne()
case false:
obj = ClassTwo()
}
You now have obj as either a ClassOne instance or a ClassTwo instance. But there's a problem. You don't know which it is. The AnyObject typing preserves the real underlying type (polymorphism), but it also hides the type. Every time you use obj, you will have to test whether it is a ClassOne or a ClassTwo and cast it to that type in order to use it.
if obj is ClassOne {
(obj as! ClassOne).doSomethingClassOneKnowsHowToDo()
}
The question is: is the pain worth the gain? I would suggest that your desire to do this in the first place is probably a Bad Smell and you should revise your intended architecture. Strict static typing is the point of Swift; you are wrong to want to throw it away.
You could use the ternary operator to do it quickly, however you do need to do it every time:
let b = (a == 0) ? ClassOne() : ClassTwo() //If a==0 let b = ClassOne if not let b= ClassTwo.
#dasblinkenlight solution is great but you can also do like this
protocol MyProto {
var x: Int { get }
}
class A: MyProto {
var x = 10
var y = 10
}
class B: MyProto {
var x = 20
}
class Demo {
var type: MyProto!
init(str: String) {
if str == "0" {
type = A()
} else {
type = B()
}
}
}
....
let obj = Demo(str: "0").type
print(obj.x)

What is analogue of Objective C static variable in Swift?

It was very convenient to have static variables in Objective C (static variable's value is maintained throughout all function/method calls), however I couldn't find anything like this in Swift.
Is there anything like this?
This is an example of static variable in C:
void func() {
static int x = 0;
/* x is initialized only once across four calls of func() and
the variable will get incremented four
times after these calls. The final value of x will be 4. */
x++;
printf("%d\n", x); // outputs the value of x
}
int main() { //int argc, char *argv[] inside the main is optional in the particular program
func(); // prints 1
func(); // prints 2
func(); // prints 3
func(); // prints 4
return 0;
}
After seeing your updated answer, here is the modification for your Objective-C code:
func staticFunc() {
struct myStruct {
static var x = 0
}
myStruct.x++
println("Static Value of x: \(myStruct.x)");
}
Call is anywhere in your class
staticFunc() //Static Value of x: 1
staticFunc() //Static Value of x: 2
staticFunc() //Static Value of x: 3
Declare the variable at the top level of a file (outside any classes) which is called global variable.
variables at the top level of a file are initialised lazily! So you
can set the default value for your variable to be the result of
reading the file, and the file won't actually be read until your code
first asks for the variable's value.
Reference from HERE.
UPDATE:
From your C example you can achieve same thing in swift this way:
var x = 0 //this is global variable
func staticVar() {
x++
println(x)
}
staticVar()
x //1
staticVar()
x //2
staticVar()
x //3
Tested with playground.
From Apple Document:
In C and Objective-C, you define static constants and variables
associated with a type as global static variables. In Swift, however,
type properties are written as part of the type’s definition, within
the type’s outer curly braces, and each type property is explicitly
scoped to the type it supports.
You define type properties with the static keyword. For computed type
properties for class types, you can use the class keyword instead to
allow subclasses to override the superclass’s implementation. The
example below shows the syntax for stored and computed type
properties:
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
// return an Int value here
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
// return an Int value here
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
// return an Int value here
}
class var overrideableComputedTypeProperty: Int {
// return an Int value here
}
}
NOTE
The computed type property examples above are for read-only computed type >properties, but you can also define read-write computed
type properties with the same syntax as for computed instance
properties.