Previous Entry Share Next Entry
Temperarily Saving Properties and Restoring them Later
anime
he_the_great

Reddit directed me toward another post by Rob Pike, "Self-referential functions and the design of options." I didn't quite grasp the problem being solved. This lead me to restating the problem as best I could make out. Someone was nice enough to point out an additional requirement, "you are leaking implementation details." As I've written this I see that it was not all properties than needed restored, but only the last one, I will leave it as is though.

The desire is to be able to configure an object with many settings, then return to the previous settings. The client side should be unaware how these properties are implemented, and it shouldn't require creating a bunch of temperaries.

Rob uses the term "option," I think this is likely to lead to confusion with the "option type" and have chosen to refer to them as "properties," because that is what they are. It seems Rob is using the term "option" because an object has many properties there are several options of configuration. I'm going to end up with a little mixture of both, sorry.

I was going to try implementing the version used in Go, but this relies on a recursive alias not supported in D. I'll highlight some Go code below that I struggled to read.

dlang
alias option delegate(ref Foo) option;

Using Properties

My initial reaction lead to what I considered to be clean client code which fulfilled the problem as described in the title. Since this didn't satisfy the need to encapsulate the implementation, I set out to write the implementation.

dlang
    auto prev = foo.Option;
    foo.Option.Verbosity = Foo.Verbosity.full;
    foo.Option.Val2 = "hello";
    foo.DoSomeDebugging();
    foo.Option = prev;

The above code will be the implementation inside a function call, the behavior should not influence the calling function, which will be main in this case.

dlang
import std.stdio;

@safe:
void main() {
    Foo foo = new Foo();
    foo.Option.Val2 = "bye";
    myFun(foo);
    foo.DoSomeDebugging();
}

The expectation is that we will see "bye" printed last. Rob states, "I've tried most of the obvious ways: option structs, lots of methods, variant constructors, and more, and found them all unsatisfactory." An option struct sounds like it would fulfill most of the requirements and was what I had in mind when I wrote the first example.

dlang
struct Properties {
    string _val2;
    Foo.Verbosity Verbosity;
    @property string Val2() {
        return _val2;
    }
    @property void Val2(string v) {
        _val2 = v;
    }
}

A structure will give much of the desires. It is a value type making it easy to get a copy and more complex modifications can be made through property functions.

dlang
class Foo {
private:
    Properties p;
public:
    enum Verbosity { error, warn, hiphop, full }
    void DoSomeDebugging() @trusted {
        writeln();
        writeln("Verbose: ", p.Verbosity);
        writeln("Val2: ", p.Val2);
    }

The properties are going to just be a field for our Foo type. So there are no worries if Foo is passed to a function which doesn't take Foo by ref, you can make Foo a class if it makes you feel better.

I also provide a enum of values for verbosity, something not available in Go. And I have a function which prints values for display.

dlang
    // Declare a type which holds reference to Foo's
    // properties. Since Foo is a structure, this
    // wrapper shouldn't exist once Foo goes out of
    // scope, since use will result in segfault.
    struct Wrapper {
        Properties* prop;
        // The wrapper can convert to a saved property
        alias save this;
        @property void Verbosity(Foo.Verbosity fv) {
            prop.Verbosity = fv;
        }
        @property void Val2(string val) {
            prop.Val2 = val;
        }
        @property Properties save() {
            return *prop;
        }
    }

The Wrapper is the driving force behind easy modification and restoration. It must store a pointer to a Properties struct so that modifications can be made to Foo when the properties are changed. It also provides a save method, I'll get to in a bit.

dlang
    @property auto Option() {
        return Wrapper(&p);
    }

    @property void Option(Properties prop) {
        p = prop;
    }
}

Finishing up our Foo, provide means to access and store the properties, but not directly.

dlang
void myFun(ref Foo foo) {
    auto prev = foo.Option.save;
    scope(exit) foo.Option = prev;
    // Use proper typing while I'm at it
    foo.Option.Verbosity = Foo.Verbosity.full;
    foo.Option.Val2 = "hello";
    foo.DoSomeDebugging();
}

The function which modifies some of Foo's properties had to receive a number of changes. Instead of storing the Option value directly, we call save. This call to save removes that internal pointer giving the Properties directly. Now when we make modifications to the Options in Foo, the stored propriets in prev can be returned by assigning it over the options.

Conclusion

Personally I'm not familiar with this need. For setting a lot of properties I like myself a struct, if you want to save and restore many of these properties, again I like a struct.

I've noticed that the solution used in the blog only restores the last modified property. This approach in D allows all the values to be restored and as an explicit save which shows what is happening.

I think the ammount of boiler plate is the same for both Go and this aproarch in D. Some meta programming in D would help, but then you're not really taking advantage of this whole hiding of the implementation (right now I'm just forwarding function calls.

Go Remarks

The blog walks through the process several design progressions. It doesn't give a working chunk of code, so it makes it harder for me to put the pieces together.

golang

// Option sets the options specified.
// It returns an option to restore the last arg's previous value.
func (f *Foo) Option(opts ...option) (previous option) {
  for _, opt := range opts {
      previous = opt(f)
  }
  return previous
}

The main thing which I was hung up on was the return type (previous option). While it is nice and descriptive, I didn't realize that you could name your return arugments in Go (haven't checked if that is what it is doing). It didn't help that funcName(args)(args) is more familiar to me.

I already have some familiarity with this, but </i>func (f *Foo)</i> was also throwing me off. This is how a function is declared for a type, meaning you can call f.Option() instead of Option(f)

Finally taking in the whole line was skewed by the fact that the only thing capitalized was the function name and Foo. I'm use to user types being named with a capital letter. Couple that with reversing everything and the whole thing reads like:

Function which takes a foo and returns an Option who takes opts and returns a tuple with previous and option.

It is slight exageration, but with my low familiarity with the syntax it doesn't come naturally to see it as:

Foo's function named Option, which takes any number of options as opts and will return an option we have named previous.


?

Log in