Skip to content

Commit 96e2718

Browse files
[V4] Semigroup (#109)
* semigroup pass 1 tmp comment todo wip comments notes more comments typos detail questions slicker impl of indirect comment tmp comments REVERT fix comment bump std::versions split projected hidden indirect value type add comments explaining async invoke defaultable projectable better names more renames refactor format renames renames trim re-order split apart indirect algorithm concepts header comments comment playing with ideas summary of ideas pass 1 refine semigroup rm old constraints move to semigroup file remove export use concept rm dead tests semigroup tests concept split expand tests * comments * consistant refs * commutative * spell
1 parent e78f3ca commit 96e2718

10 files changed

Lines changed: 437 additions & 23 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ target_sources(libfork_libfork
7575
src/core/concepts/invocable.cxx
7676
src/core/concepts/awaitable.cxx
7777
src/core/concepts/indirect.cxx
78+
src/core/concepts/semigroup.cxx
7879
src/core/frame.cxx
7980
src/core/task.cxx
8081
src/core/ops.cxx
@@ -105,6 +106,7 @@ target_sources(libfork_libfork
105106
# libfork.algorithm
106107
src/algorithm/algorithm.cxx
107108
src/algorithm/for_each.cxx
109+
src/algorithm/concepts.cxx
108110
PRIVATE
109111
src/exception.cpp
110112
)

src/algorithm/algorithm.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export module libfork.algorithm;
22

3+
export import :concepts;
34
export import :for_each;

src/algorithm/concepts.cxx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export module libfork.algorithm:concepts;
2+
3+
import std;
4+
5+
import libfork.core;
6+
7+
namespace lf {
8+
9+
export template <typename T>
10+
concept sized_random_access_range = std::ranges::random_access_range<T> && std::ranges::sized_range<T>;
11+
12+
} // namespace lf

src/algorithm/for_each.cxx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ import std;
66

77
import libfork.core;
88

9-
namespace lf {
9+
import :concepts;
1010

11-
template <typename T>
12-
concept sized_random_access_range = std::ranges::random_access_range<T> && std::ranges::sized_range<T>;
11+
namespace lf {
1312

1413
// TODO: relax the constraints?
1514
//
@@ -40,7 +39,7 @@ struct for_each_impl {
4039
LF_ASSUME(len >= 0);
4140

4241
if (len <= n) {
43-
if constexpr (indirectly_async_regular_unary_invocable<Fn, Context, I>) {
42+
if constexpr (async::indirectly_regular_unary_invocable<Fn, Context, I>) {
4443
// Prefer async
4544
auto sc = co_await scope();
4645
for (; head != tail; ++head) {
@@ -75,7 +74,7 @@ struct for_each_impl {
7574
case 0:
7675
co_return;
7776
case 1:
78-
if constexpr (indirectly_async_regular_unary_invocable<Fn, Context, I>) {
77+
if constexpr (async::indirectly_regular_unary_invocable<Fn, Context, I>) {
7978
auto sc = co_await scope();
8079
co_await sc.call_drop(fn, *head);
8180
co_await sc.join();

src/core/concepts/indirect.cxx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ using indirect_value_t = indirect_value<I>::type;
3232

3333
// ========= Core concepts =========
3434

35+
namespace sync {
36+
3537
/**
3638
* @brief A version of `std::indirectly_unary_invocable` that supports
3739
* libfork's projection type.
3840
*/
3941
export template <typename Fn, typename I>
40-
concept indirectly_sync_unary_invocable = //
42+
concept indirectly_unary_invocable = //
4143
std::indirectly_readable<I> && //
4244
std::copy_constructible<Fn> && //
4345
std::invocable<Fn &, indirect_value_t<I>> && //
@@ -52,7 +54,7 @@ concept indirectly_sync_unary_invocable = //
5254
* libfork's projection type.
5355
*/
5456
export template <typename Fn, typename I>
55-
concept indirectly_sync_regular_unary_invocable = //
57+
concept indirectly_regular_unary_invocable = //
5658
std::indirectly_readable<I> && //
5759
std::copy_constructible<Fn> && //
5860
std::regular_invocable<Fn &, indirect_value_t<I>> && //
@@ -62,12 +64,16 @@ concept indirectly_sync_regular_unary_invocable = //
6264
std::invoke_result_t<Fn &, std::iter_reference_t<I>> //
6365
>; //
6466

67+
} // namespace sync
68+
69+
namespace async {
70+
6571
/**
6672
* @brief A variant of `std::indirectly_unary_invocable` that supports
6773
* libfork's projection type and requires an async invocable.
6874
*/
6975
export template <typename Fn, typename Context, typename I>
70-
concept indirectly_async_unary_invocable = //
76+
concept indirectly_unary_invocable = //
7177
worker_context<Context> && //
7278
std::indirectly_readable<I> && //
7379
std::copy_constructible<Fn> && //
@@ -83,7 +89,7 @@ concept indirectly_async_unary_invocable = //
8389
* libfork's projection type and requires an async invocable.
8490
*/
8591
export template <typename Fn, typename Context, typename I>
86-
concept indirectly_async_regular_unary_invocable = //
92+
concept indirectly_regular_unary_invocable = //
8793
worker_context<Context> && //
8894
std::indirectly_readable<I> && //
8995
std::copy_constructible<Fn> && //
@@ -94,6 +100,8 @@ concept indirectly_async_regular_unary_invocable = //
94100
async_result_t<Fn &, Context, std::iter_reference_t<I>> //
95101
>; //
96102

103+
} // namespace async
104+
97105
/**
98106
* @brief A variant of `std::indirectly_unary_invocable` that supports either
99107
* sync or async invocables.
@@ -103,7 +111,7 @@ concept indirectly_async_regular_unary_invocable = //
103111
*/
104112
export template <typename Fn, typename Context, typename I>
105113
concept indirectly_unary_invocable =
106-
indirectly_async_unary_invocable<Fn, Context, I> || indirectly_sync_unary_invocable<Fn, I>;
114+
async::indirectly_unary_invocable<Fn, Context, I> || sync::indirectly_unary_invocable<Fn, I>;
107115

108116
// clang-format off
109117

@@ -116,7 +124,7 @@ concept indirectly_unary_invocable =
116124
*/
117125
export template <typename Fn, typename Context, typename I>
118126
concept indirectly_regular_unary_invocable =
119-
indirectly_async_regular_unary_invocable<Fn, Context, I> || indirectly_sync_regular_unary_invocable<Fn, I>;
127+
async::indirectly_regular_unary_invocable<Fn, Context, I> || sync::indirectly_regular_unary_invocable<Fn, I>;
120128

121129
// clang-format on
122130

src/core/concepts/semigroup.cxx

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
export module libfork.core:concepts_semigroup;
2+
3+
import std;
4+
5+
import :concepts_invocable;
6+
import :concepts_indirect;
7+
8+
namespace lf {
9+
10+
// === Semigroup
11+
12+
namespace sync {
13+
14+
template <typename R, typename Fn, typename... Args>
15+
concept invocable_to = std::invocable<Fn, Args...> && std::same_as<std::invoke_result_t<Fn, Args...>, R>;
16+
17+
template <typename R, typename Fn, typename T>
18+
concept semigroup_r = //
19+
std::constructible_from<R, T> && //
20+
invocable_to<R, Fn, T, T> && //
21+
invocable_to<R, Fn, T, R> && //
22+
invocable_to<R, Fn, R, T> && //
23+
invocable_to<R, Fn, R, R>; //
24+
25+
template <typename R, typename Fn, typename I>
26+
concept indirect_semigroup_r = //
27+
semigroup_r<R, Fn &, indirect_value_t<I>> && //
28+
semigroup_r<R, Fn &, std::iter_reference_t<I>> && //
29+
invocable_to<R, Fn &, indirect_value_t<I>, std::iter_reference_t<I>> && //
30+
invocable_to<R, Fn &, std::iter_reference_t<I>, indirect_value_t<I>>; //
31+
32+
/**
33+
* @brief A semigroup is a set `S` and an associative binary operation `·`, such that `S` is closed under `·`.
34+
*
35+
* Associativity means that for all `a, b, c` in `S`, `(a · b) · c = a · (b · c)`.
36+
*
37+
* Example: `(Z, +)` is a semigroup, since we can add any two integers and the result is also an integer.
38+
*
39+
* Example: `(Z, /)` is not a semigroup, since `2/3` s not an integer.
40+
*
41+
* Example: `(Z, -)` is not a semigroup, since `(1 - 1) - 1 != 1 - (1 - 1)`.
42+
*
43+
* Let `t`, `u` and `f` be objects of types `T`, `U` and `Fn` respectively.
44+
* Then the following expression must be valid:
45+
*
46+
* ```
47+
* f(u, t)
48+
* ```
49+
*
50+
* And return the same type `R` for all combinations of `T` and `U` being `R`,
51+
* `indirect_value_t<I>` and `std::iter_reference_t<I>`.
52+
*/
53+
export template <typename Fn, typename I>
54+
concept indirect_semigroup = //
55+
std::indirectly_readable<I> && //
56+
std::copy_constructible<Fn> && //
57+
std::regular_invocable<Fn &, indirect_value_t<I>, indirect_value_t<I>> && //
58+
indirect_semigroup_r< //
59+
std::invoke_result_t<Fn &, indirect_value_t<I>, indirect_value_t<I>>, //
60+
Fn, //
61+
I //
62+
>; //
63+
64+
} // namespace sync
65+
66+
namespace async {
67+
68+
template <typename R, typename Fn, typename Context, typename T>
69+
concept semigroup_r = //
70+
std::constructible_from<R, T> && //
71+
std::default_initializable<R> && //
72+
async_invocable_to<Fn, R, Context, T, T> && //
73+
async_invocable_to<Fn, R, Context, T, R> && //
74+
async_invocable_to<Fn, R, Context, R, T> && //
75+
async_invocable_to<Fn, R, Context, R, R>; //
76+
77+
template <typename R, typename Fn, typename Context, typename I>
78+
concept indirect_semigroup_r = //
79+
semigroup_r<R, Fn &, Context, indirect_value_t<I>> && //
80+
semigroup_r<R, Fn &, Context, std::iter_reference_t<I>> && //
81+
async_invocable_to<Fn &, R, Context, indirect_value_t<I>, std::iter_reference_t<I>> && //
82+
async_invocable_to<Fn &, R, Context, std::iter_reference_t<I>, indirect_value_t<I>>; //
83+
84+
/**
85+
* @brief A semigroup is a set `S` and an associative binary operation `·`, such that `S` is closed under `·`.
86+
*
87+
* Associativity means that for all `a, b, c` in `S`, `(a · b) · c = a · (b · c)`.
88+
*
89+
* Example: `(Z, +)` is a semigroup, since we can add any two integers and the result is also an integer.
90+
*
91+
* Example: `(Z, /)` is not a semigroup, since `2/3` s not an integer.
92+
*
93+
* Example: `(Z, -)` is not a semigroup, since `(1 - 1) - 1 != 1 - (1 - 1)`.
94+
*
95+
* Let `t`, `u` and `f` be objects of types `T`, `U` and `Fn` respectively.
96+
* Then the following expression must be valid:
97+
*
98+
* ```
99+
* R ret;
100+
* co_await scope.call(std::addressof(ret), f, u, t)
101+
* ```
102+
*
103+
* And return the same type `R` for all combinations of `T` and `U` being `R`,
104+
* `indirect_value_t<I>` and `std::iter_reference_t<I>`.
105+
*/
106+
export template <typename Fn, typename Context, typename I>
107+
concept indirect_semigroup = //
108+
std::indirectly_readable<I> && //
109+
worker_context<Context> && //
110+
std::copy_constructible<Fn> && //
111+
async_invocable<Fn &, Context, indirect_value_t<I>, indirect_value_t<I>> && //
112+
indirect_semigroup_r< //
113+
async_result_t<Fn &, Context, indirect_value_t<I>, indirect_value_t<I>>, //
114+
Fn, //
115+
Context, //
116+
I //
117+
>; //
118+
119+
} // namespace async
120+
121+
/**
122+
* @brief Either a synchronous or asynchronous semigroup.
123+
*/
124+
export template <typename Fn, typename Context, typename I>
125+
concept indirect_semigroup = async::indirect_semigroup<Fn, Context, I> || sync::indirect_semigroup<Fn, I>;
126+
127+
/**
128+
* @brief A semantic requirement that the semigroup operation is commutative.
129+
*
130+
* Commutativity requires `a · b = b · a` for all `a`, `b` in the set `S`.
131+
*/
132+
export template <typename Fn, typename Context, typename I>
133+
concept indirect_commutative_semigroup = indirect_semigroup<Fn, Context, I>;
134+
135+
template <typename Fn, typename Context, typename I>
136+
struct indirect_semigroup_result {
137+
using type = std::invoke_result_t<Fn &, indirect_value_t<I>, indirect_value_t<I>>;
138+
};
139+
140+
template <typename Fn, typename Context, typename I>
141+
requires async::indirect_semigroup<Fn, Context, I>
142+
struct indirect_semigroup_result<Fn, Context, I> {
143+
using type = async_result_t<Fn &, Context, indirect_value_t<I>, indirect_value_t<I>>;
144+
};
145+
146+
/**
147+
* @brief Get the result type of an indirect semigroup operation.
148+
*
149+
* This is the type of the result of applying the semigroup operation to two elements of the set.
150+
*/
151+
export template <typename Fn, typename Context, typename I>
152+
requires indirect_semigroup<Fn, Context, I>
153+
using indirect_semigroup_t = indirect_semigroup_result<Fn, Context, I>::type;
154+
155+
} // namespace lf

src/core/core.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export import :concepts_context;
66
export import :concepts_stack;
77
export import :concepts_awaitable;
88
export import :concepts_indirect;
9+
export import :concepts_semigroup;
910
export import :frame;
1011
export import :task;
1112
export import :thread_locals;

src/core/projected.cxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct projected_impl {
3838

3939
// async
4040
template <typename I, typename Fn, typename Context>
41-
requires indirectly_async_unary_invocable<Fn, Context, I>
41+
requires async::indirectly_unary_invocable<Fn, Context, I>
4242
struct projected_impl<I, Fn, Context> {
4343
struct type : conditional_difference_type<I> {
4444
private:
@@ -63,15 +63,15 @@ concept async_defaultable_impl =
6363

6464
template <typename Fn, typename Context, typename I>
6565
concept indirect_async_defaultable =
66-
!indirectly_async_regular_unary_invocable<Fn, Context, I> || async_defaultable_impl<Fn, Context, I>;
66+
!async::indirectly_regular_unary_invocable<Fn, Context, I> || async_defaultable_impl<Fn, Context, I>;
6767

6868
/**
6969
* @brief Test if `I` can be projected through `Fn` in the context of `Context`
7070
*
7171
* This requires the standard indirectly regular invocable and, in addition,
7272
* async projections must return a default initializable type.
7373
*
74-
* Projectable && indirectly_async_unary_invocable implies an async projection
74+
* Projectable && async::indirectly_unary_invocable implies an async projection
7575
* with default initializable async result type.
7676
*/
7777
export template <typename Fn, typename Context, typename I>

0 commit comments

Comments
 (0)