Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is there a better way to change any fields' value with only one Void? #592

Closed
stnbSim opened this issue Sep 23, 2019 · 5 comments
Closed

Comments

@stnbSim
Copy link

stnbSim commented Sep 23, 2019

Hi,

What is the best practice to change field value dynamically in dart?
The below code looks fine for a class has 2 - 4 fields, but it will be too much repetition when the class is growing.

class BadExample {
  
  String x = 'this is X';
  String y = 'this is y';
  String z = 'this is z';
  
  void changeFieldValue(String field, String val){
    switch(field){
      case 'x':
        x = val;
        break;
      case 'y':
        y = val;
        break;
      case 'z':
        z = val;
        break;
    }
  }
}

do dart have a feature like below code ?
I am sorry if this is not a right place to ask.
thanks

void changeFieldValue(String field, String val) => this[field] = val;

@eernstg
Copy link
Member

eernstg commented Sep 23, 2019

The ability to access fields of an object as if it had been a map from something (say, Strings) to some kind of values is quite JavaScript-like (cf., for instance, this page), and it does not blend in very well with a statically typed style of programming.

Maybe you actually just want to use a Map<String, Something> where Something could be dynamic if you wish to maintain a dynamic style?

In general, Dart has been moving in the direction of static typing for quite a while now (and we plan to move further in that direction with features like 'non-null by default' and variance). So there are a lot of good reasons for thinking really hard about your software design, and trying to do what you need to do without this kind of inherently-dynamic features.

@lrhn
Copy link
Member

lrhn commented Sep 23, 2019

Indeed. What you are doing is a kind of reflection where you use a run-time string value to represent a source name. The only way to refer to a variable by source name in Dart is to use the name in the source code. At run-time, the names are no longer available (you may see them in error messages or similar, but only as values, not as a way to refer to the thing).

What you can do here is either the switch case you have, or some other structure like:

var _settes = { 
  "x" : (String value) { x = value; },
  "y" : (String value) { y = value; },
  "z" : (String value) { z = value; },
};
void changeFieldValue(String field, String value) { 
  _setters[field](value);
}

In any case, the x = value must occur in your source code, otherwise you cannot have an assignment to x. (Well, without using dart:mirrors, but that's not available on all platforms).

@stnbSim
Copy link
Author

stnbSim commented Sep 23, 2019

@eernstg thanks for the explanation,
i will probably go for @lrhn solution at this stage

@stnbSim stnbSim closed this as completed Sep 23, 2019
@PetarKirov
Copy link

[..] it does not blend in very well with a statically typed style of programming [..]

I disagree. Before I started using Dart (with Flutter) my primary language was a completely statically-typed one (and one that doesn't even have a built-in dynamic type) that actually supports these kinds of scenarios particularly well, without using any sort of run-time reflection or otherwise extensive runtime features that are JavaScript-like.

Here's a 15 line changeFieldValue implementation that is generic in 2 dimensions - works with any type of object and allows assigning to fields of any type:

void changeFieldValue(Object, Field)(ref Object obj, string fieldName, Field newFieldValue)
{
    final switch (fieldName)
    {
        static foreach (idx, field; Object.tupleof)
        {
            case field.stringof:
            {
                static if (is(typeof(field) == Field))
                    obj.tupleof[idx] = newFieldValue;
                return;
            }
        }
    }
}

(final switch throws an error if the value is not matched by any of the cases, so we don't need to manually write a default case.)

The only "special" runtime feature (compared to C) used here is the ability to switch on strings which Dart already has. All the rest happens at compile-time:

  1. The compiler supports program introspection during compilation. This allows things like obtaining the list of fields for any struct or class in the program source, querying their type, name, memory size, memory alignment, etc.
  2. Compile-time parameters. Functions, among other entities in the language can have two sets of parameters - compile-time and run-time. Applying compile-time arguments to a function (also known as template instantiation) essentially generates a new function for each distinct set of compile-time arguments. This is necessary, so we can generate different code (on demand) for each possible field assignment that we want to support.
  3. Compile-time code-generation. While most languages go the route of separate build-time step for code generation, having metaprogramming be part of the language allows writing much more expressive and versatile code. For this particular use case we actually need two much less general, but still very powerful features - static if and static foreach, which are surprisingly similar to the newly introduced in Dart 2.3 Control Flow Collections feature.

What we're doing in the code snippet above is generating the list of switch case-s from the list (aka tuple) of fields of the given Object type and only generating the field assignment code if the two types match. In Dart-speak you can imagine that the switch cases is our collection and that static foreach is a collection-for and static if is a collection-if applied to switch cases collection.

The end result, given a class type like e.g. class C { int a; int b; } would be:

void changeFieldValue(ref C obj, string fieldName, int newFieldValue)
{
    final switch (fieldName)
    {
        case "a":
            obj.a = newFieldValue;
            return;
        case "b":
            obj.b = newFieldValue;
            return;
    }
}

I really wish Dart could support these features as they greatly reduce the amount of boilerplate code and dramatically increase the design space for library authors.


Here's a full example: https://run.dlang.io/gist/ZombineDev/e5f2ab17ff0a05ea342c7c7e37e77ce3?compiler=dmd

@eernstg
Copy link
Member

eernstg commented Sep 24, 2019

@ZombineDev wrote:

Before I started using Dart (with Flutter) my primary language was
a completely statically-typed one .. that actually supports these kinds
of scenarios particularly well,

Interesting stuff! Static meta-programming (with access to information about the resolution of name applications to name declarations, as well as static analysis results like typing information) can allow for very elegant solutions, and the integration with the language and the compilation process can allow for a smooth development workflow. We have discussed adding support for various kinds of static meta-programming to Dart, and it would be so cool, but it's a large feature and we don't have concrete proposals.

For now, we'd have to use more traditional code generation techniques (which may use the analyzer to get access to the results of static analysis) in order to get similar outcomes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants