首页 文章

修改不可变的子结构

提问于
浏览
6

假设我有一个不可变的包装器:

template<class T>
struct immut {
  T const& get() const {return *state;}
  immut modify( std::function<T(T)> f ) const { return immut{f(*state)}; }
  immut(T in):state(std::make_shared<T>(std::move(in))){}
private:
  std::shared_ptr<T const> state;
};

如果我有 immut<Bob> b ,我可以将 Bob(Bob) 操作变成可以替换我的 b 的操作 .

template<class T>
std::function<immut<T>(immut<T>)> on_immut( std::function<void(T&)> f ){
  return [=](auto&&in){ return in.modify( [&](auto t){ f(t); return t; } ); };
}

因此,如果 Bobint x,y; ,我可以将天真可变代码 [](auto& b){ b.x++; } 变成 immut<Bob> 更新程序 .


现在,如果 Bob 反过来拥有 immut<Alice> 成员,那么会有 immut<Charlie> 成员 .

假设我有一个 Charlie 更新程序, void(Charlie&) . 而且我知道 Charlie 我要更新的地方是 . 在可变的土地上,它看起来像:

void update( Bob& b ){
  modify_charlie(b.a.c[77]);
}

我可能会把它分成:

template<class S, class M>
using get=std::function<M&(S&)>;
template<class X>
using update=std::function<void(X&)>;
template<class X>
using produce=std::function<X&()>;

void update( produce<Bob> b, get<Bob, Alice> a, get<Alice, Charlie> c, update<Charlie> u ){
  u(c(a(b())));
}

甚至去代数并有(伪代码):

get<A,C> operator|(get<A,B>,get<B,C>);
update<A> operator|(get<A,B>,update<B>);
produce<B> operator|(produce<A>,get<A,B>);
void operator|(produce<A>, update<A>);

让我们根据需要将操作链接在一起 .

void update( produce<Bob> b, get<Bob, Alice> a, get<Alice, Charlie> c, update<Charlie> u ){std::
  u(c(a(b())));
}

b|a|c|u;

步骤可以在任何地方拼接在一起 .


与immuts相同的是什么,它有一个名字吗?理想情况下,我希望这些步骤在可变的情况下与孤立但可组合一样,天真的“叶子代码”在可变状态结构上 .

1 回答

  • 6

    immuts的等价物是什么,有没有名字?

    IDK是否存在子状态操作的通用名称 . 但是在Haskell中,有一个Lens可以提供你想要的 .

    在C中,您可以将 lens 视为一对getter和setter函数,这两个函数都只关注其直接子部分 . 然后有一个作曲家将两个镜头组合在一起,聚焦在一个结构的更深的子部分上 .

    // lens for `A` field in `D`
    template<class D, class A>
    using get = std::function<A const &(D const &)>;
    
    template<class D, class A>
    using set = std::function<D(D const &, A)>;
    
    template<class D, class A>
    using lens = std::pair<get<D, A>, set<D, A>>;
    
    // compose (D, A) lens with an inner (A, B) lens,
    // return a (D, B) lens
    template<class D, class A, class B>
    lens<D, B>
    lens_composer(lens<D, A> da, lens<A, B> ab) {
        auto abgetter = ab.first;
        auto absetter = ab.second;
        auto dagetter = da.first;
        auto dasetter = da.second;
    
        get<D, B> getter = [abgetter, dagetter]
            (D const &d) -> B const&
        {
            return abgetter(dagetter(d));
        };
    
        set<D, B> setter = [dagetter, absetter, dasetter]
            (D const &d, B newb) -> D
        {
            A const &a = dagetter(d);
            A newa = absetter(a, newb);
            return dasetter(d, newa);
        };
    
        return {getter, setter};
    };
    

    你可以写一个像这样的基本镜头:

    struct Bob {
        immut<Alice> alice;
        immut<Anna>  anna;
    };
    auto bob_alice_lens
    = lens<Bob, Alice> {
        [] (Bob const& b) -> Alice const & {
            return b.alice.get();
        },
        [] (Bob const& b, Alice newAlice) -> Bob {
            return { immut{newAlice}, b.anna };
        }
    };
    

    请注意,此过程可以通过宏自动执行 .

    然后,如果 Bob 包含 immut<Alice>Alice 包含 immut<Charlie> ,则可以写入2个镜头( BobAliceAliceCharlie ), BobCharlie 镜头可以由以下组成:

    auto bob_charlie_lens = 
    lens_composer(bob_alice_lens, alice_charlie_lens);
    

    Live Demo

    NOTE:

    Here是一个更完整的示例,线性内存增长w.r.t深度,没有类型擦除开销( std::function ),并且具有完整的编译时类型检查 . 它还使用宏来生成基本镜头 .

相关问题