MVCoconut / coconut.data

Observable Data Layer.
The Unlicense
20 stars 8 forks source link

Is there something to know about Option<T>? #80

Closed grepsuzette closed 2 years ago

grepsuzette commented 2 years ago

Weird thing here:

import coconut.Ui.hxx;
import coconut.ui.*;
using tink.CoreApi;

class T implements coconut.data.Model  {
    public static function main() {
        var doc = js.Browser.document;
        var div = doc.createDivElement();
        js.Browser.document.body.appendChild( div);
        var world = new World();
        haxe.Timer.delay( ()->world.loadIt(), 1000 );
        var prov1 = new FruitsProvider(world.season);
        Renderer.mount( div, hxx('<V prov1={prov1} />'));
    }
}

class World implements coconut.data.Model {
    @:observable var season : Option<Season> = @byDefault None;
    @:transition public function loadIt() return { season: Some(new Season("autumn")) }
}

class Season implements coconut.data.Model {
    @:editable var name:String;
    public function new(name:String) this = { name:name }
}

class FruitsProvider implements coconut.data.Model {
    @:external var season: Option<Season>;
    public function new(season:Option<Season>) this = { season:season }   // HERE!
}

class V extends coconut.ui.View {
    @:attr var prov1 : FruitsProvider;
    function render()
        <>
            <p>Season: 
                <switch {prov1.season}>
                    <case {Some(season)}> {season.name}
                    <case {None}> unknown yet
                </switch>
            </p>
        </>
    ;
}
grepsuzette commented 2 years ago

After I posted this, I tried to remove the type in the constructor of one of my classes (been struggling for weeks, there were like 4 rewrites) and suddenly all the app works and updates magically!

The build macro for Model working around the constructor maybe has a bug? (I hope it's a bug and not a type issue)

back2dos commented 2 years ago

Yeah, this one is a bit tricky.

The problem here is that @:external consumes a coconut.data.Value which is essentially an Observable with a @:from macro cast that will wrap non-observable expressions in Observable.auto(() -> $expr).

If you remove the type hint, then season is inferred to Value<Option<Season>> (which is the correct type to use here).

Thus var prov1 = new FruitsProvider(world.season); compiles to var prov1 = new FruitsProvider(Observable.auto(() -> world.season));.

If you leave the type as it is, then you get var prov1 = new FruitsProvider(world.season); and that only passes the current value to the FruitsProvider and its constructor implementation will be generated with this = { season: Observable.auto(() -> season) } - observing a function argument does not cause updates.

Sorry for the trouble. The upcoming v1 will axe this footgun: https://github.com/MVCoconut/coconut.data/issues/77