1- """Calculator that can register and compose operations."""
1+ """Calculator utilities for applying and composing operations.
2+
3+ The :class:`Calculator` provides a thin layer over :class:`OperationRegistry`.
4+ It supports applying named operations, composing unary operations into a
5+ callable, and a small 'chain' helper for mixed sequences of unary/binary
6+ operations used by the examples and tests.
7+
8+ Keep the behaviour minimal: methods raise :class:`OperationError` for
9+ operation-related failures.
10+ """
211
312from typing import Callable , Iterable , List , Any , Optional
413
817
918
1019class Calculator :
11- """A tiny calculator that uses an OperationRegistry.
20+ """Thin calculator wrapper around an OperationRegistry.
1221
13- Features :
14-
15- - register operations (via registry or helper)
16- - apply a named operation to arguments
17- - compose a sequence of operations into a single callable
22+ Methods :
23+ register(op, replace=False): Register an Operation.
24+ apply(op_name, *args): Apply a registered operation.
25+ compose(ops, left_to_right=True): Compose unary operations.
26+ chain( sequence, initial): Apply a mixed sequence DSL-style.
1827 """
1928
2029 def __init__ (self , registry : Optional [OperationRegistry ] = None ):
@@ -24,9 +33,22 @@ def __init__(self, registry: Optional[OperationRegistry] = None):
2433 self .registry = registry
2534
2635 def register (self , op : Operation , * , replace : bool = False ) -> None :
36+ """Register an :class:`Operation` with the calculator's registry.
37+
38+ Args:
39+ op: Operation to register.
40+ replace: If True, replace an existing operation with the same name.
41+ """
42+
2743 self .registry .register (op , replace = replace )
2844
2945 def apply (self , op_name : str , * args : Any ) -> Any :
46+ """Apply a named operation to the provided arguments.
47+
48+ Raises:
49+ OperationError: wraps underlying errors from the operation call.
50+ """
51+
3052 op = self .registry .get (op_name )
3153 try :
3254 return op (* args )
@@ -38,17 +60,11 @@ def apply(self, op_name: str, *args: Any) -> Any:
3860 def compose (
3961 self , ops : Iterable [str ], * , left_to_right : bool = True
4062 ) -> Callable [[Any ], Any ]:
41- """Compose a sequence of unary operations into a single callable.
42-
43- The composed function takes one argument and applies the operations in order.
44- Only unary operations (arity == 1) are supported for composition.
45-
46- Args:
47- ops: iterable of operation names
48- left_to_right: if True, apply first op then next (f2(f1(x))).
63+ """Compose a sequence of unary operations into a callable.
4964
50- Returns:
51- callable f(x)
65+ Only supports unary operations (arity == 1). The returned function
66+ accepts a single value and applies the operations in the requested
67+ order.
5268 """
5369
5470 op_list : List [Operation ] = [self .registry .get (name ) for name in ops ]
@@ -77,16 +93,11 @@ def composed(x):
7793 def chain (self , sequence : Iterable [str ], initial : Any ) -> Any :
7894 """Apply a mixed sequence of operations to an initial value.
7995
80- For binary operations, the operation consumes (current_value, next_input)
81- and returns a new current_value. To support binary ops in a chain, the
82- sequence should alternate between operation names and provided literals
83- (which are interpreted as inputs). Example::
84-
85- sequence = ['add', 5, 'sqr']
86- chain(sequence, initial=2) -> sqr(add(2,5)) = (2+5)^2
87-
88- This is a very small DSL useful for demos.
96+ The sequence may contain operation names (str) and literal values.
97+ Binary operations consume the current value and the next literal.
98+ This helper is intentionally small and tailored for tests/examples.
8999 """
100+
90101 seq = list (sequence )
91102 cur = initial
92103 i = 0
0 commit comments