I have a Dart enum which looks like this:
enum Gender {
#JsonValue(0)
male,
#JsonValue(1)
female,
}
I have created a dart extension that returns the String name and int value. It looks something like this -
extension GenderExtention on Gender {
String get name {
switch (this) {
default:
return _getDefaultName(this);
}
}
//For the enum male, it returns "Male"
String _getDefaultName(Community value) {
if (value == null) {
return null;
}
String valueStr = value.toString();
String enumName = valueStr.substring(valueStr.indexOf('.') + 1);
return enumName[0].toUpperCase() + enumName.substring(1);
}
int get value {
switch (this) {
case Gender.male:
return 0;
case Gender.female:
return 1;
default:
return null;
}
}
}
This becomes painful for larger enums, especially the value section.
Are there any suggestions on how to get the enum value (0 for #JsonValue(0)) more easily than manually defining it in the extension? Would using something like reflections help here?
the only way to access annotations at run-time is indeed reflection using dart:mirrors. That library is not available when compiling to the web or for Flutter, so it's probably not going to solve your problem.
What you can do here is:
int get value => this.index;
That only works if the values are actually the same as the index (0 for the first declared value, 1 for the next, etc.)
Related
I want to achieve fast enumeration so that I can express this enum as a contiguous monotonic sequence of unique integer values, one for each case (starting at zero). I want the order of assignments to be in the same order as they appear in the declaration.
In this particular example, the sequence would simply be 0, 1 and 2. Is there an expression I can use to achieve this? Or is it only possible by manually typing out this by hand?
I want to have two hashable implementations, so I can establish all possible varieties or simply how many cases I have.
enum Score: {
case snap(Rank) // 6 varieties
case double(Power) // 4 varieties
case highFive
var value: Int {
// ...
}
}
enum Rank: Int {
//..
}
enum Power: Int {
// ..
}
It seems this is not possible.
I either choose to maintain two enums that are identical except one has associated types and the other does not.
Or I manually implement the solution.
Manual
So for a poker game, I would establish an order of importance for each hand, as per the rules of Texas Hold'em.
private extension Hand {
var precedence: Int {
switch self {
case .highCard:
return 0
case .pair:
return 1
case .twoPair:
return 2
case .threeKind:
return 3
case .straight:
return 4
case .fourKind:
return 5
case .flush:
return 6
case .fullHouse:
return 7
case .straightFlush:
return 8
case .royalFlush:
return 9
}
}
}
When the above is in place, I can then produce a toggle.
extension Hand: CaseIterable {
public static var allCases: [Self] {
let ranks = Rank.allCases
return ranks.map(highCard) +
ranks.map(pair) +
ranks.map(twoPair) +
ranks.map(threeKind) +
ranks.map(straight) +
ranks.map(fourKind) +
ranks.map(flush) +
ranks.map(fullHouse) +
ranks.map(straightFlush) +
[royalFlush]
}
/// Establish every unique case.
/// - Parameter ignore: If true, do not unique by rank.
/// - Returns: Every possible case, with or without rank as a uniquing restriction.
public static func allCases(ignoringRank: Bool) -> [Self] {
switch ignoringRank {
case true:
var tally = Set<Int>()
return Self.allCases.filter {
switch tally.contains($0.precedence) {
case true:
return false
case false:
tally.insert($0.precedence)
return true
}
}
case false:
return Self.allCases
}
}
}
The following is now possible.
private extension Hand {
static var assessments: [Assessable.Type] {
allCases(ignoringRank: true).map {
$0.assessment
}
}
private var assessment: Assessable.Type {
switch self {
case .highCard:
return HighCardAssessor.self
case .pair:
return PairAssessor.self
case .twoPair:
return TwoPairAssessor.self
case .threeKind:
return ThreeKindAssessor.self
case .straight:
return StraightAssessor.self
case .fourKind:
return FourKindAssessor.self
case .flush:
return FlushAssessor.self
case .fullHouse:
return FullHouseAssessor.self
case .straightFlush:
return StraightFlushAssessor.self
case .royalFlush:
return RoyalFlushAssessor.self
}
}
}
An assessor is a type that implements evaluate(cards:) to determine if a hand exists. So being able to loop like this is useful.
what I did:
extension BigDate on DateTime {
String get locatedWeekDay {
switch (weekday) {
case DateTime.sunday:
return "Sun";
case DateTime.monday:
return "Mon";
case DateTime.tuesday:
return "Tue";
......
default:
throw Exception();
}
}
}
class JapanDate extends DateTime {
#override
String get locatedWeekDay {
switch (weekday) {
case DateTime.sunday:
return "日";
case DateTime.monday:
return "月";
......
default:
throw Exception();
}
}
}
now I just run this:
DateTime d = JapanDate(2022, 3, 2);
print(d.locatedWeekDay);
it returns me "Wed" oh, can you help me to fix it?
I tried: to add #override to the get method, add the import to the first line.
Extension methods are static; they are compile-time syntactic sugar for an equivalent freestanding function. As such, they are wholly dependent on the static type of the variable (whether explicitly declared or inferred), which in your case is DateTime. They cannot be overridden since overrides involve runtime polymorphism.
What you could do instead is:
Create a base class (or mixin) that provides the locatedWeekDay interface.
Make JapanDate derive from that interface.
Make your extension method check if this implements that interface, falling back to a default implementation if it's an ordinary DateTime object.
abstract class HasLocatedWeekDay {
String get locatedWeekDay;
}
extension BigDate on DateTime {
String get locatedWeekDay {
// Type-promotion for `this` is not yet supported.
// See: <https://github.com/dart-lang/language/issues/1397>
final self = this;
if (self is HasLocatedWeekDay) {
return self.locatedWeekDay;
}
switch (weekday) {
case DateTime.sunday:
return "Sun";
case DateTime.monday:
return "Mon";
case DateTime.tuesday:
return "Tue";
......
default:
throw Exception();
}
}
}
class JapanDate extends DateTime implements HasLocatedWeekDay {
#override
String get locatedWeekDay {
switch (weekday) {
case DateTime.sunday:
return "日";
case DateTime.monday:
return "月";
......
default:
throw Exception();
}
}
}
Just don't cast it to DateTime leave it as JapanDate and the code above should work as you expect it to.
JapanDate d = JapanDate(2022, 3, 2);
print(d.locatedWeekDay);
There is no locatedWeekDay method on DateTime. Therefore, it can't be overridden, but you can create a custom class extending this or use an extension as you did for BigDate.
To create a custom class, you need to pass data to super class.
class JapanDate extends DateTime {
JapanDate(int year,
[int month = 1,
int day = 1,
int hour = 0,
int minute = 0,
int second = 0,
int millisecond = 0,
int microsecond = 0])
: super(
year,
month = month,
day = day,
hour = hour,
minute = minute,
second = second,
millisecond = millisecond,
microsecond = microsecond);
String get locatedWeekDay {
switch (this.weekday) {
case DateTime.sunday:
return "日";
case DateTime.monday:
return "月";
//....
default:
return "N";
}
}
}
Now you can use JapanDate class with locatedWeekDay.
JapanDate d = JapanDate(2022, 3, 7);
print(d.locatedWeekDay); ///月
About .weekday on DateTime. It is defined as
external int get weekday;
External Functions
An external function is a function whose body is provided separately from its
declaration. An external function may be a top-level function (17), a method
You can follow this What does external mean in Dart?
This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 12 months ago.
I have migrated my Dart code to NNBD / Null Safety. Some of it looks like this:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
This causes two analysis errors. For _a += 'a';:
An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.
For Bar() {:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
In both cases I have already done exactly what the error suggests! What's up with that?
I'm using Dart 2.12.0-133.2.beta (Tue Dec 15).
Edit: I found this page which says:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
But that doesn't make sense to me - there's only one possible flow control path from if (_a != null) to _a += 'a'; in this case - there's no async code and Dart is single-threaded - so it doesn't matter that _a isn't local.
And the error message for Bar() explicitly states the possibility of initialising the field in the constructor.
The problem is that class fields can be overridden even if it is marked as final. The following example illustrates the problem:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
#override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
The B class overrides the text final field so it returns a value the first time it is asked but returns null after this. You cannot write your A class in such a way that you can prevent this form of overrides from being allowed.
So we cannot change the return value of getText from String? to String even if it looks like we checks the text field for null before returning it.
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
It seems like this really does only work for local variables. This code has no errors:
class Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
It kind of sucks though. My code is now filled with code that just copies class members to local variables and back again. :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Ah so it turns out a "field initializer" is actually like this:
class Bar {
Bar() : _a = 'a';
String _a;
}
There are few ways to deal with this situation. I've given a detailed answer here so I'm only writing the solutions from it:
Use local variable (Recommended)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
Use ??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
Use Bang operator (!)
You should only use this solution when you're 100% sure that the variable (a) is not null at the time you're using it.
void foo() {
a = a! + 'a'; // <-- Bang operator
}
To answer your second question:
Non-nullable fields should always be initialized. There are generally three ways of initializing them:
In the declaration:
class Bar {
String a = 'a';
}
In the initializing formal
class Bar {
String a;
Bar({required this.a});
}
In the initializer list:
class Bar {
String a;
Bar(String b) : a = b;
}
You can create your classes in null-safety like this
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}
i need to evaluate what type is a variable to make some switch,there are any way to evaluate a varible to get his type, like val() or something similar. i need to do something for integers and other for string.
i alreaedy try to using a switch, like this,
switch (selector) {
case int :
print('value is a integer');
break;
case String:
print('value is a String');
break;
}
but how i do this, if switch can allow compare mixed type of vars?
thank you
You can use the keyword is or switch over runtimeType :
dynamic foo = 42;
if (foo is int) {
print("Hello");
}
switch (foo.runtimeType) {
case int: {
print("World");
}
}
Consider using is instead of directly using runtimeType. As is works with subclasses. While using runtimeType is a strict comparison.
You can use something like :
if(selector.runtimeType == int) print("Hello")
It's very simple:
dynamic a = "hello";
if (a.runtimeType == int)
print("a is int");
else if (a.runtimeType == String)
print("a is String");
Create this extension:
extension EvaluateType on Object? {
bool get isInt => this is int;
bool get isString => this is String;
}
Usage:
void main() {
Object? foo;
foo = 0;
print(foo.isInt); // true
print(foo.isString); // false
}
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'}]