Skip to content

Commit

Permalink
Add lazy duplex apply.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmadden committed Oct 10, 2020
1 parent 962005c commit 3f5d284
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 11 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ add_custom_target(
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS single_header_tester)

# On Linux debug builds, the proper CMake test associated with the unit tests
# On Clang debug builds, the proper CMake test associated with the unit tests
# includes test coverage reporting.
if(IS_CLANG AND CMAKE_BUILD_TYPE STREQUAL "Debug")
string(REPLACE clang llvm-profdata LLVM_PROFDATA $ENV{CC})
Expand Down
74 changes: 73 additions & 1 deletion src/alia/signals/application.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,72 @@ lazy_lift(Function f)
return [=](auto... args) { return lazy_apply(f, std::move(args)...); };
}

// duplex_lazy_apply(forward, reverse, arg), where :arg is a duplex signal,
// yields another duplex signal whose value is the result of applying :forward
// to the value of :arg. Writing to the resulting signal applies :reverse and
// writes the result to :arg.
// The applications in both directions are done lazily, on demand.

namespace detail {

template<class Result, class Forward, class Reverse, class Arg>
struct lazy_duplex_apply_signal
: lazy_signal<
lazy_duplex_apply_signal<Result, Forward, Reverse, Arg>,
Result,
duplex_signal>
{
lazy_duplex_apply_signal(Forward forward, Reverse reverse, Arg arg)
: forward_(std::move(forward)),
reverse_(std::move(reverse)),
arg_(std::move(arg))
{
}
id_interface const&
value_id() const
{
return arg_.value_id();
}
bool
has_value() const
{
return arg_.has_value();
}
Result
movable_value() const
{
return forward_(forward_signal(arg_));
}
bool
ready_to_write() const
{
return arg_.ready_to_write();
}
void
write(Result value) const
{
arg_.write(reverse_(std::move(value)));
}

private:
Forward forward_;
Reverse reverse_;
Arg arg_;
};

} // namespace detail

template<class Forward, class Reverse, class Arg>
auto
lazy_duplex_apply(Forward forward, Reverse reverse, Arg arg)
{
return detail::lazy_duplex_apply_signal<
decltype(forward(read_signal(arg))),
Forward,
Reverse,
Arg>(std::move(forward), std::move(reverse), std::move(arg));
}

// apply(ctx, f, args...), where :args are all signals, yields a signal to the
// result of applying the function :f to the values of :args. Unlike
// lazy_apply, this is eager and caches and the result.
Expand Down Expand Up @@ -253,6 +319,12 @@ lift(Function f)
};
}

// duplex_apply(ctx, forward, reverse, arg), where :arg is a duplex signal,
// yields another duplex signal whose value is the result of applying :forward
// to the value of :arg. Writing to the resulting signal applies :reverse and
// writes the result to :arg.
// Similar to apply(ctx, f, arg), this is eager and caches the forward result.

namespace detail {

template<class Value, class Wrapped, class Reverse>
Expand Down Expand Up @@ -290,7 +362,7 @@ struct duplex_apply_signal : signal<
return wrapped_.ready_to_write();
}
void
write(typename Value value) const
write(Value value) const
{
wrapped_.write(reverse_(std::move(value)));
}
Expand Down
72 changes: 63 additions & 9 deletions unit_tests/signals/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,60 @@ TEST_CASE("lazy_apply", "[signals][application]")
REQUIRE(s3.value_id() != s4.value_id());
}

TEST_CASE("duplex_lazy_apply", "[signals][application]")
{
alia::system sys;
initialize_system(sys, [](context) {});

int n = 0;

captured_id signal_id;

auto make_controller = [&](double new_value) {
return [=, &n, &signal_id](context ctx) {
auto f = [](int x) -> double { return x * 2.0; };
auto r = [](double x) -> int { return int(x / 2.0 + 0.5); };

auto s = lazy_duplex_apply(f, r, direct(n));

typedef decltype(s) signal_t;
REQUIRE(signal_is_readable<signal_t>::value);
REQUIRE(signal_is_writable<signal_t>::value);

REQUIRE(signal_has_value(s));
REQUIRE(read_signal(s) == n * 2.0);

signal_id.capture(s.value_id());

REQUIRE(s.ready_to_write());
if (new_value > 0)
write_signal(s, new_value);
};
};

do_traversal(sys, make_controller(0));
REQUIRE(n == 0);

{
captured_id last_id = signal_id;
do_traversal(sys, make_controller(4));
REQUIRE(n == 2);

do_traversal(sys, make_controller(4));
REQUIRE(n == 2);
REQUIRE(last_id != signal_id);
}

do_traversal(sys, make_controller(0));
REQUIRE(n == 2);

do_traversal(sys, make_controller(2));
REQUIRE(n == 1);

do_traversal(sys, make_controller(0));
REQUIRE(n == 1);
}

TEST_CASE("failing lazy_apply", "[signals][application]")
{
clear_log();
Expand Down Expand Up @@ -280,15 +334,7 @@ TEST_CASE("lift", "[signals][application]")
}
}

TEST_CASE("alia_mem_fn", "[signals][application]")
{
auto v = value("test text");
REQUIRE(read_signal(lazy_apply(ALIA_MEM_FN(length), v)) == 9);
REQUIRE(
read_signal(lazy_apply(alia_mem_fn(substr), v, value(5))) == "text");
}

TEST_CASE("duplex apply", "[signals][application]")
TEST_CASE("duplex_apply", "[signals][application]")
{
alia::system sys;
initialize_system(sys, [](context) {});
Expand Down Expand Up @@ -341,3 +387,11 @@ TEST_CASE("duplex apply", "[signals][application]")
do_traversal(sys, make_controller(0));
REQUIRE(n == 1);
}

TEST_CASE("alia_mem_fn", "[signals][application]")
{
auto v = value("test text");
REQUIRE(read_signal(lazy_apply(ALIA_MEM_FN(length), v)) == 9);
REQUIRE(
read_signal(lazy_apply(alia_mem_fn(substr), v, value(5))) == "text");
}

0 comments on commit 3f5d284

Please sign in to comment.