MayaFlux 0.1.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
OperationChain.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::Yantra {
6
7/**
8 * @concept ExecutorConcept
9 * @brief Defines requirements for executor types that can be used with FluentExecutor
10 */
11template <typename T>
12concept ExecutorConcept = requires(T t) {
13 { t.template execute<int, int, int>(std::declval<int>()) };
14};
15
16/**
17 * @class FluentExecutor
18 * @brief Fluent interface for chaining operations on any executor
19 *
20 * Provides a composable, type-safe way to chain operations together.
21 * This class is executor-agnostic and can work with any type that
22 * satisfies the ExecutorConcept.
23 *
24 * Key Features:
25 * - Type-safe operation chaining with compile-time verification
26 * - Support for both type-based and named operations
27 * - Custom function application within the chain
28 * - Multiple terminal operations for different use cases
29 * - Error accumulation and reporting
30 *
31 * Usage:
32 * ```cpp
33 * auto result = executor.with(input_data)
34 * .then<Transformer>()
35 * .apply([](auto& data) { return preprocess(data); })
36 * .then<Analyzer>("my_analyzer")
37 * .get();
38 * ```
39 */
40template <typename Executor, ComputeData DataType>
41class MAYAFLUX_API FluentExecutor {
42public:
43 using executor_type = Executor;
44 using data_type = DataType;
45
46 /**
47 * @brief Construct with executor and initial data
48 * @param executor Shared pointer to the executor instance
49 * @param input Initial data to process
50 */
51 FluentExecutor(std::shared_ptr<Executor> executor, const DataType& input)
52 : m_executor(std::move(executor))
53 , m_data(input)
54 , m_successful(true)
55 {
56 if (!m_executor) {
57 throw std::invalid_argument("FluentExecutor requires non-null executor");
58 }
59 }
60
61 /**
62 * @brief Move constructor for efficiency
63 */
64 FluentExecutor(std::shared_ptr<Executor> executor, DataType&& input)
65 : m_executor(std::move(executor))
66 , m_data(std::move(input))
67 , m_successful(true)
68 {
69 if (!m_executor) {
70 throw std::invalid_argument("FluentExecutor requires non-null executor");
71 }
72 }
73
74 /**
75 * @brief Chain operation execution by type
76 * @tparam OpClass Operation class to execute
77 * @tparam OutputType Expected output type (defaults to current DataType)
78 * @return New FluentExecutor with transformed data
79 * @throws std::runtime_error if operation fails
80 */
81 template <typename OpClass, ComputeData OutputType = DataType>
83 {
84 if (!m_successful) {
85 throw std::runtime_error("Cannot continue chain after failed operation");
86 }
87
88 try {
89 auto result = m_executor->template execute<OpClass, DataType, OutputType>(m_data);
90 if (!result) {
91 m_successful = false;
92 record_error("Operation " + std::string(typeid(OpClass).name()) + " failed");
93 throw std::runtime_error("Operation failed in fluent chain: " + std::string(typeid(OpClass).name()));
94 }
95
96 auto next = FluentExecutor<Executor, OutputType>(m_executor, std::move(result->data));
97 next.m_operation_history = m_operation_history;
98 next.m_operation_history.push_back(typeid(OpClass).name());
99 return next;
100 } catch (const std::exception& e) {
101 m_successful = false;
102 record_error(e.what());
103 throw;
104 }
105 }
106
107 /**
108 * @brief Chain named operation
109 * @tparam OpClass Operation class to execute
110 * @tparam OutputType Expected output type
111 * @param name Name of the operation in the pool
112 * @return New FluentExecutor with transformed data
113 */
114 template <typename OpClass, ComputeData OutputType = DataType>
116 {
117 if (!m_successful) {
118 throw std::runtime_error("Cannot continue chain after failed operation");
119 }
120
121 try {
122 auto result = m_executor->template execute<OpClass, DataType, OutputType>(name, m_data);
123 if (!result) {
124 m_successful = false;
125 record_error("Named operation '" + name + "' failed");
126 throw std::runtime_error("Named operation failed in fluent chain: " + name);
127 }
128
129 auto next = FluentExecutor<Executor, OutputType>(m_executor, std::move(result->data));
130 next.m_operation_history = m_operation_history;
131 next.m_operation_history.push_back(name);
132 return next;
133 } catch (const std::exception& e) {
134 m_successful = false;
135 record_error(e.what());
136 throw;
137 }
138 }
139
140 /**
141 * @brief Apply custom transformation function
142 * @tparam Func Function type
143 * @param func Transformation function to apply
144 * @return New FluentExecutor with transformed data
145 */
146 template <typename Func>
147 requires std::invocable<Func, const DataType&>
148 auto apply(Func&& func)
149 {
150 if (!m_successful) {
151 throw std::runtime_error("Cannot continue chain after failed operation");
152 }
153
154 using ResultType = std::invoke_result_t<Func, const DataType&>;
155
156 try {
157 auto result = std::forward<Func>(func)(m_data);
158 auto next = FluentExecutor<Executor, ResultType>(m_executor, std::move(result));
159 next.m_operation_history = m_operation_history;
160 next.m_operation_history.push_back("custom_function");
161 return next;
162 } catch (const std::exception& e) {
163 m_successful = false;
164 record_error(std::string("Custom function failed: ") + e.what());
165 throw;
166 }
167 }
168
169 /**
170 * @brief Apply function with side effects (doesn't change data type)
171 * @param func Function to apply for side effects
172 * @return Reference to this executor for chaining
173 */
174 template <typename Func>
175 requires std::invocable<Func, DataType&>
176 FluentExecutor& tap(Func&& func)
177 {
178 if (!m_successful) {
179 throw std::runtime_error("Cannot continue chain after failed operation");
180 }
181
182 try {
183 std::forward<Func>(func)(m_data);
184 m_operation_history.emplace_back("tap");
185 return *this;
186 } catch (const std::exception& e) {
187 m_successful = false;
188 record_error(std::string("Tap function failed: ") + e.what());
189 throw;
190 }
191 }
192
193 /**
194 * @brief Conditional execution
195 * @tparam OpClass Operation to execute if condition is true
196 * @param condition Condition to check
197 * @return FluentExecutor with possibly transformed data
198 */
199 template <typename OpClass>
200 FluentExecutor& when(bool condition)
201 {
202 if (condition && m_successful) {
203 return then<OpClass>();
204 }
205 return *this;
206 }
207
208 /**
209 * @brief Conditional execution with predicate
210 * @tparam OpClass Operation to execute if predicate returns true
211 * @param predicate Function to evaluate condition
212 * @return FluentExecutor with possibly transformed data
213 */
214 template <typename OpClass, typename Pred>
215 requires std::predicate<Pred, const DataType&>
216 FluentExecutor& when(Pred&& predicate)
217 {
218 if (m_successful && std::forward<Pred>(predicate)(m_data)) {
219 return then<OpClass>();
220 }
221 return *this;
222 }
223
224 /**
225 * @brief Fork execution into multiple paths
226 * @tparam OpClasses Operation classes to execute in parallel
227 * @return Tuple of results from each operation
228 */
229 template <typename... OpClasses>
230 auto fork()
231 {
232 if (!m_successful) {
233 throw std::runtime_error("Cannot fork after failed operation");
234 }
235
236 return std::make_tuple(
237 m_executor->template execute<OpClasses, DataType>(m_data)...);
238 }
239
240 /**
241 * @brief Get the final result
242 * @return Const reference to the processed data
243 */
244 const DataType& get() const
245 {
246 if (!m_successful) {
247 throw std::runtime_error("Cannot get result from failed chain");
248 }
249 return m_data;
250 }
251
252 /**
253 * @brief Get mutable reference to the result
254 * @return Mutable reference to the processed data
255 */
256 DataType& get_mutable()
257 {
258 if (!m_successful) {
259 throw std::runtime_error("Cannot get result from failed chain");
260 }
261 return m_data;
262 }
263
264 /**
265 * @brief Move the result out
266 * @return Moved data
267 */
268 DataType consume() &&
269 {
270 if (!m_successful) {
271 throw std::runtime_error("Cannot consume result from failed chain");
272 }
273 return std::move(m_data);
274 }
275
276 /**
277 * @brief Extract to IO wrapper with metadata
278 * @return IO-wrapped data with execution metadata
279 */
281 {
282 IO<DataType> result(m_data);
283 result.metadata["execution_history"] = m_operation_history;
284 result.metadata["successful"] = m_successful;
285 if (!m_errors.empty()) {
286 result.metadata["errors"] = m_errors;
287 }
288 return result;
289 }
290
291 /**
292 * @brief Get or provide default value
293 * @param default_value Value to return if chain failed
294 * @return Processed data or default
295 */
296 DataType get_or(const DataType& default_value) const
297 {
298 return m_successful ? m_data : default_value;
299 }
300
301 /**
302 * @brief Get or compute default value
303 * @param generator Function to generate default value
304 * @return Processed data or generated default
305 */
306 template <typename Generator>
307 requires std::invocable<Generator>
308 DataType get_or_else(Generator&& generator) const
309 {
310 return m_successful ? m_data : std::forward<Generator>(generator)();
311 }
312
313 /**
314 * @brief Check if all operations succeeded
315 */
316 [[nodiscard]] bool is_successful() const { return m_successful; }
317
318 /**
319 * @brief Get operation history
320 */
321 [[nodiscard]] const std::vector<std::string>& get_history() const { return m_operation_history; }
322
323 /**
324 * @brief Get accumulated errors
325 */
326 [[nodiscard]] const std::vector<std::string>& get_errors() const { return m_errors; }
327
328 /**
329 * @brief Get the executor
330 */
331 std::shared_ptr<Executor> get_executor() const { return m_executor; }
332
333 /**
334 * @brief Reset with new data
335 * @param new_data New initial data
336 * @return Reference to this executor
337 */
338 FluentExecutor& reset(const DataType& new_data)
339 {
340 m_data = new_data;
341 m_successful = true;
342 m_operation_history.clear();
343 m_errors.clear();
344 return *this;
345 }
346
347private:
348 std::shared_ptr<Executor> m_executor;
349 DataType m_data;
351 std::vector<std::string> m_operation_history;
352 std::vector<std::string> m_errors;
353
354 void record_error(const std::string& error)
355 {
356 m_errors.push_back(error);
357 }
358};
359
360/**
361 * @brief Helper to create FluentExecutor with type deduction
362 */
363template <typename Executor, ComputeData DataType>
364auto make_fluent(std::shared_ptr<Executor> executor, DataType&& data)
365{
367 std::move(executor),
368 std::forward<DataType>(data));
369}
370
371} // namespace MayaFlux::Yantra
auto fork()
Fork execution into multiple paths.
FluentExecutor(std::shared_ptr< Executor > executor, DataType &&input)
Move constructor for efficiency.
FluentExecutor & when(Pred &&predicate)
Conditional execution with predicate.
FluentExecutor & reset(const DataType &new_data)
Reset with new data.
const std::vector< std::string > & get_history() const
Get operation history.
FluentExecutor & tap(Func &&func)
Apply function with side effects (doesn't change data type)
std::shared_ptr< Executor > get_executor() const
Get the executor.
DataType get_or(const DataType &default_value) const
Get or provide default value.
auto apply(Func &&func)
Apply custom transformation function.
DataType consume() &&
Move the result out.
FluentExecutor & when(bool condition)
Conditional execution.
std::shared_ptr< Executor > m_executor
const std::vector< std::string > & get_errors() const
Get accumulated errors.
bool is_successful() const
Check if all operations succeeded.
DataType get_or_else(Generator &&generator) const
Get or compute default value.
FluentExecutor< Executor, OutputType > then(const std::string &name)
Chain named operation.
std::vector< std::string > m_errors
FluentExecutor(std::shared_ptr< Executor > executor, const DataType &input)
Construct with executor and initial data.
DataType & get_mutable()
Get mutable reference to the result.
FluentExecutor< Executor, OutputType > then()
Chain operation execution by type.
const DataType & get() const
Get the final result.
IO< DataType > to_io() const
Extract to IO wrapper with metadata.
std::vector< std::string > m_operation_history
void record_error(const std::string &error)
Fluent interface for chaining operations on any executor.
Defines requirements for executor types that can be used with FluentExecutor.
auto make_fluent(std::shared_ptr< Executor > executor, DataType &&data)
Helper to create FluentExecutor with type deduction.
std::unordered_map< std::string, std::any > metadata
Associated metadata.
Definition DataIO.hpp:28
Input/Output container for computation pipeline data flow with structure preservation.
Definition DataIO.hpp:24