“The limits of my language are the limits of my world.”Ludwig Wittgenstein
using room_id = int;
struct room {
string name;
bool light_on;
};
struct house {
vector<room> rooms;
vector<unique_ptr<rooms>> rooms;
vector<shared_ptr<rooms>> rooms;
vector<shared_ptr<rooms>> rooms;
vector<rooms> rooms;
unordered_map<room_id, rooms> rooms;
unordered_map<room_id, rooms> rooms;
vector<pair<room*,room*>> doors;
vector<pair<room*,room*>> doors;
vector<pair<weak_ptr<room>, weak_ptr<room>>> doors;
vector<pair<weak_ptr<room>, weak_ptr<room>>> doors;
vector<pair<room_id, room_id>> doors;
boost::multi_index<room_id,room_id> doors;
boost::multi_index<room_id,room_id> doors;
};
struct room {
string name;
bool light_on;
boost::signal<bool> on_light_on_changed;
};
struct house {
vector<shared_ptr<rooms> rooms;
vector<pair<weak_ptr<room>, weak_ptr<room>>> doors;
};
struct room_component
{
Widget::Button button;
room_component(std::shared_ptr<room> r, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([r] {
r->light_on = !r->light_on;
r->on_light_on_changed(light_on);
});
auto update = [=] {
button.SetText(r->light_on ? "Turn off!" : "Turn on!");
};
room_->on_light_on_changed.connect(update);
update();
}
};
template <typename T>
struct state
{
state(T init = T{})
: node_{std::make_shared({std::move(init)})}
{}
const T& get() const {
return node_->curr;
}
void set(T v) {
using std::swap;
swap(node_->curr, v);
node_->on_change(v, node_->curr);
}
friend auto watch(state& s, std::function<const T&, const T&> cb) {
return node_->on_change.connect(std::move(cb));
}
private:
struct node {
T curr;
boost::signal<const T&, const T&> on_change;
};
std::shared_ptr<node> node_;
};
struct room_component
{
Widget::Button button;
room_component(std::shared_ptr<room> r, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([r] {
r->light_on = !r->light_on;
r->on_light_on_changed(light_on);
});
auto update = [=] {
button.SetText(r->light_on ? "Turn off!" : "Turn on!");
};
room_->on_light_on_changed.connect(update);
update();
}
};
struct room_component
{
Widget::Button button;
room_component(state<house> s, room_id id, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([s] {
auto h = s.get();
h.rooms[id].light_on = !h.rooms[id].light_on;
s.set(h);
});
auto update = [=] (auto&& h) {
button.SetText(h.rooms[id].light_on ? "Turn off!" : "Turn on!");
};
watch(s, [] (auto&&, auto&& h) { update(h); });
update(s.get());
}
};
struct room_component
{
Widget::Button button;
template <typename T, typename Lens>
room_component(state<T> s, Lens l, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([s] {
auto light_on = view(s.get(), l);
s.set(put(s.get(), l, !light_on));
});
auto update = [=] (auto&& v) {
button.SetText(view(v, l) ? "Turn off!" : "Turn on!");
};
watch(s, [] (auto&&, auto&& v) { update(v); });
update(s.get());
}
};
template <typename Whole, typename Part>
using lens = std::pair<
function<Part(Whole)>, // getter
function<Whole(Whole, Part)>, // setter
>;
template <typename Whole, typename Lens>
auto view(Whole w, Lens l) {
return l.first();
}
template <typename Whole, typename Lens, typename Part>
auto put(Whole w, Lens l, Part p) {
return l.second(w, p);
}
auto room_light_on(room_id id) {
return pair{
[=] (house h) {
return h.rooms[id].light_on;
},
[=] (house h, bool on) {
h.rooms[id].light_on = on;
return h;
}
};
}
auto st = state<house>{};
auto c0 = room_component{st, room_light_on(0), ...};
auto c1 = room_component{st, room_light_on(1), ...};
// ...
auto room_light_on(room_id id) {
return attr(&house::rooms)
| at_(id)
| attr(&room::light_on);
}
auto st = state<house>{};
auto c0 = room_component{st, room_light_on(0), ...};
auto c1 = room_component{st, room_light_on(1), ...};
...
Lenses defined as one single function,
composable with $(f \circ g)(x) = f(g(x))$
struct room_component
{
Widget::Button button;
template <typename T, typename Lens>
room_component(state<T> s, Lens l, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([s] {
auto light_on = view(s.get(), l);
s.set(put(s.get(), l, !light_on));
});
auto update = [=] (auto&& v) {
button.SetText(view(v, l) ? "Turn off!" : "Turn on!");
};
watch(s, [] (auto&&, auto&& v) { update(v); });
update(s.get());
}
};
template <typename T>
struct state
{
const T& get();
void set(T v);
friend auto watch(state& s, std::function<const T&, const T&> cb);
template <typename Lens>
auto zoom(Lens l)
-> cursor<decltype(view(get(), l))>;
};
template <typename T>
struct cursor
{
const T& get();
void set(T v);
friend auto watch(state& s, std::function<const T&, const T&> cb);
template <typename Lens>
auto zoom(Lens l)
-> cursor<decltype(view(get(), l))>;
private:
struct node { // pseudo-code
std::shared_ptr<node>> parent;
std::vector<std::weak_ptr<node>> children;
T curr;
boost::signal<const T&, const T&> on_change;
};
std::shared_ptr<node> node_;
};
struct room_component
{
Widget::Button button;
room_component(cursor<bool> s, Widget::View& parent)
: button{parent}
{
button.on_clicked.connect([s] {
s.set(!s.get());
});
auto update = [=] (bool on) {
button.SetText(on ? "Turn off!" : "Turn on!");
};
watch(s, [] (bool, bool on) { update(on); });
update(s.get());
}
};
auto st = state<house>{};
auto c0 = room_component{
st.zoom(attr(&house::rooms)
| at_(0)
| attr(&room::light_on)),
...
};
auto st = state<house>{};
auto c0 = room_component{
st.zoom(attr(&house::rooms))
.zoom(at_(0))
.zoom(&room::light_on),
...
};
auto st = state<house>{};
auto c0 = room_component{
st[&house::rooms][0][&room::light_on],
...
};
cat dedupe distinct drop drop_while filter interpose mapcat map map_indexed partition_by partition random_sample remove replace take take_nth take_while | |
chain count cycle enumerate product range repeat | |
Exclusive! |
each eager interleave iter readbuf read sink traced transducer unzip writebuf write zip |
model.cpp
class Item : public QObject
{
Q_OBJECT
bool done_ = 0;
...
public:
Q_PROPERTY(bool done
READ getDone
WRITE setDone
NOTIFY doneChanged)
int getDone() const {
return done_;
}
void setDone(bool v) {
done_ = v;
Q_EMIT doneChanged();
}
Q_SIGNAL doneChanged();
};
gui.qml
Window {
id: root
Todo.Item {
id: model
}
Text {
text: model.text
color: model.done ? "grey" : "black"
}
Button {
text: model.done ? "Mark" : "Unmark"
onClick: model.done = !model.done
}
}
namespace todo {
struct item
{
bool done = false;
std::string text;
};
struct model
{
std::string name;
immer::flex_vector<item> todos;
};
model add_item(model m, std::string text);
model remove_item(model m, std::size_t index);
model load(const std::string& fname);
model save(const std::string& fname, const model& todos);
} // namespace todo
class Item : public QObject {
Q_OBJECT
lager::cursor<bool> done_;
lager::cursor<QString> text_;
public:
Q_PROPERTY(bool done READ getDone WRITE setDone NOTIFY doneChanged);
bool getDone() const { return done_.get(); }
void setDone(bool done) { done_.set(done); }
Q_SIGNAL void doneChanged();
Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged);
QString getText() const { return text_.get(); }
void setText(QString text) { text_.set(text); }
Q_SIGNAL void textChanged();
Item(lager::cursor<todo::item> data)
: done_{data[&todo::item::done]}
, text_{data[&todo::item::text].xf(
zug::map([](auto x) { return QString::fromStdString(x); }),
zug::map([](auto x) { return x.toStdString(); }))}
{
watch(done_, [&](auto&&...) { Q_EMIT doneChanged(); });
watch(text_, [&](auto&&...) { Q_EMIT textChanged(); });
}
};
class Item : public QObject
{
Q_OBJECT
public:
LAGER_QT_CURSOR(bool, done);
LAGER_QT_CURSOR(QString, text);
Item(lager::cursor<todo::item> data)
: LAGER_QT(done) {data[&todo::item::done]}
, LAGER_QT(text) {data[&todo::item::text].xf(
zug::map([](auto&& x) { return QString::fromStdString(x); }),
zug::map([](auto&& x) { return x.toStdString(); }))}
{}
};
class Model : public QObject
{
Q_OBJECT
lager::state<todo::model, lager::automatic_tag> state_;
public:
LAGER_QT_CURSOR(QString, name);
LAGER_QT_READER(int, count);
Model()
: LAGER_QT(name){state_[&todo::model::name].xf(
zug::map([](auto&& x) { return QString::fromStdString(x); }),
zug::map([](auto&& x) { return x.toStdString(); }))}
, LAGER_QT(count){state_.xf(zug::map(
[](auto&& x) { return (int) x.todos.size(); }))}
{}
Q_INVOKABLE Item* todo(int index)
{
return new Item{state_[&todo::model::todos][index]};
}
...
};
class Model : public QObject
{
...
Q_INVOKABLE void add(QString text)
{
state_.update([&](auto m) {
return todo::add_item(
x, text.toStdString());
});
}
Q_INVOKABLE void remove(int idx)
{
state_.update([&](auto m) {
return todo::remove_item(x, idx);
});
}
...
...
Q_INVOKABLE void save(QString fname)
{
state_.update([&](auto x) {
return todo::save(
fname.toStdString(), x);
});
}
Q_INVOKABLE void load(QString fname)
{
state_.update([&](auto x) {
return todo::load(
fname.toStdString());
});
}
};
struct item
{
bool done = false;
std::string text;
};
struct model
{
std::string name;
immer::flex_vector<item> todos;
};
class Item : public QObject
{
Q_OBJECT
Q_PROPERTY(bool done ...);
Q_PROPERTY(QString text ...);
};
class Model : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name ...);
Q_PROPERTY(int count ...);
Q_INVOKABLE Item* item(int idx);
...
};
“Utopia is on the horizon. I move two steps closer; it moves two steps away. I walk another ten steps and the horizon runs ten steps further away. As much as I may walk, I'll never reach it.
So what's the point of utopia?
That's the point—
to keep walking.”Eduardo Galeano
“Utopia is on the horizon. I move two steps closer; it moves two steps away. I walk another ten steps and the horizon runs ten steps further away. As much as I may walk, I'll never reach it.
So what's the point of utopia?
That's the point—
to keep walking.”Eduardo Galeano
template <typename Whole, typename Part>
using lens = std::pair<
function<Part(Whole)>,
function<Whole(Whole, Part)>,
>;
template <typename Whole, typename Part>
auto attr(Part Whole::* member)
{
return pair{
[=] (auto w) {
return w.*member;
}, [=] (auto w, auto p) {
w.*memeber = p;
return w;
}
};
}
Requires custom composition... $😞$
template <typename Whole, typename Part>
using lens = function<
function<Functor<Whole>(Whole)> (
function<Functor<Part>(Part)>)
>;
// pseudo-C++ for
// Functor f => (a -> f a) -> (s -> f s)
template <typename Whole, typename Part>
auto attr(Part Whole::* member)
{
return [=](auto f) {
return [=](auto w) {
return f(w.*member)([&](auto p) {
w.*member = p;
return w;
});
};
};
}
Composable with $(f \circ g)(x) = f(g(x))$
struct room_model
{
string name;
bool light_on;
};
struct toggle_light_action {};
struct ...;
using room_action = variant<
toggle_light_action,
...
>;
room_model update(room_model m,
room_action a)
{
return visit(lager::visitor{
[&] (toggle_light_action&&) {
m.light_on = !m.light_on;
return m;
},
[&] (...) { ... }
}, std::move(a));
}
void draw(const room_model& m,
lager::context<room_action> ctx)
{
if (ImGui::Button(m.light_on ? "Turn off" :
"Turn on")) {
ctx.dispatch(toggle_light_action{});
}
}
struct house_model
{
vector<room_model> rooms;
vector<pair<int, int>> doors;
};
using house_action = variant<
pair<int, room_action>,
...
>;
house_model update(house_model m,
house_action a)
{
return visit(lager::visitor{
[&] (pair<int, room_action>&& a) {
auto& r = m.rooms[a.first];
r = update(r, a.second);
return m;
},
[&] (...) { ... }
}, std::move(a));
}
void draw(const house_model& m,
lager::context<house_action> ctx)
{
auto idx = std::size_t;
for (auto&& r : m.rooms) {
draw(r, {ctx, [i = idx++] (auto a) {
return make_pair(i, a);
}});
}
}
struct model { ... };
using action = ...;
model update(model m, action a);
void draw(const model& m, lager::context<action> ctx);
int main()
{
auto store = lager::make_store<action>(
model{},
update,
lager::with_manual_event_loop{});
watch(store, [&](auto&& old, auto&& cur) {
draw(cur, store); // redraw on change
});
while (auto ev = poll()) { // poll external input
auto act = intent(ev); // map event to action
store.dispatch(act);
}
}