#2578 Idiom to update constant data classes

fraya Wed 23 Nov 2016

Using const for "data classes", I'm repeating this code to update the data.

const class Person
{
  Int id
  Str name

  new make(|This| f)
  {
    f(this)
    // put default value if absent
  }

  new makeCopy(Person that, |This| f)
  {
    this.id   = that.id
    this.name = that.name
    f(this)
  }

  This copy(|This| f)
  {
    makeCopy(this, f)
  }
}

// Usage

p1 := Person { id = 3; name = "foo" }
p2 := p.copy { name =  "bar" }

Do you use something similar? Something better? How do you do it?

SlimerDude Wed 23 Nov 2016

Hi Fraya,

Another good question!

Fantom only allows the one it-block in a ctor parameter list so in the past I've used a field map and reflection to do something like this:

const class Person {

    const Int id
    const Str name

    new make(|This| f) { f(this) }

    Person clone([Field:Obj?]? newVals := null) {
        fieldVals := Field:Obj?[:]
        this.typeof.fields.each {
            fieldVals[it] = it.get(this)
        }

        if (newVals != null)
            fieldVals.setAll(newVals)

        planFunc := Field.makeSetFunc(fieldVals)
        return Person(planFunc)
    }
}

And use it like this:

p1 := Person {    // create
    it.id = 3
    it.name = "foo"
}

p2 := p1.clone    // clone p1

p3 := p2.clone([  // clone p2 & set new name
    Person#name : "Bob"
])

You can't pass in a normal func to set the fields as you run into const issues.

brian Wed 23 Nov 2016

I personally like a constructor that takes an original instance to copy from and an it-block to tweak individual fields:

class Foo
{
  new make(Foo? orig := null, |This|? f := null)
  {
    if (orig != null)
    {
      this.a = orig.a
      this.b = orig.b
    }
    if (f != null) f(this)
  }

  const Str a := "xxx"
  const Str b := "yyy"
}

x := Foo(null) { it.a = "1" }
y := Foo(x) { it.a = "2" }

fraya Wed 23 Nov 2016

@SlimerDude

Thank you for your response, your solution can be a mixin returning This and changing the function last line to

makeFunc := this.typeof.method("make")
return makeFunc.call(planFunc)

? Can I haz a Cloneable?? :)

@andy thank you, i'll test it

Login or Signup to reply.