aboutsummaryrefslogtreecommitdiff
path: root/python/src/types/monad.py
diff options
context:
space:
mode:
authortslil <tslil@posteo.de>2025-10-14 20:48:03 +0100
committertslil <tslil@posteo.de>2025-10-14 23:28:08 +0100
commit1376a23bda5e6fe94148cb57ca1447907c6bb21e (patch)
treeab158829b82af934837b9c63825de2af001ee9ea /python/src/types/monad.py
parent723393e8921e2832123b78aa3dec2f87f96b2f74 (diff)
Sketching the implementation of the monad and selections
Diffstat (limited to 'python/src/types/monad.py')
-rw-r--r--python/src/types/monad.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/python/src/types/monad.py b/python/src/types/monad.py
new file mode 100644
index 0000000..f09ff33
--- /dev/null
+++ b/python/src/types/monad.py
@@ -0,0 +1,80 @@
+from functools import wraps
+from typing import Callable
+
+from .selection import Selection
+from .state import EditorState, Result, StateChange
+
+
+class StateMonad:
+ def __init__(self, run_fn: Callable[[EditorState], Result]):
+ self._run_fn = run_fn
+
+ def run(self, state: EditorState) -> Result:
+ return self._run_fn(state)
+
+ def commit(self, state: EditorState) -> tuple[Selection, EditorState]:
+ sel, changes = self.run(state)
+ state.commit_changes(changes)
+ return (sel, state)
+
+ def bind(self, f: Callable[[Selection], "StateMonad"]) -> "StateMonad":
+ def run_bound(state: EditorState) -> Result:
+ sel1, changes1 = self.run(state)
+ expr2 = f(sel1)
+ sel2, changes2 = expr2.run(state)
+ return (sel2, changes1 + changes2)
+
+ return StateMonad(run_bound)
+
+ @staticmethod
+ def pure(selection: Selection) -> "StateMonad":
+ def run_pure(_: EditorState) -> Result:
+ return (selection, [])
+
+ return StateMonad(run_pure)
+
+ def modify(change: StateChange, selection: Selection) -> "StateMonad":
+ def run_modify(_: EditorState) -> Result:
+ return (selection, [change])
+
+ return StateMonad(run_modify)
+
+
+def monadic(
+ func: Callable[[Selection, EditorState], Result],
+) -> Callable[[Selection | StateMonad], StateMonad]:
+ def make_monad(sel: Selection) -> StateMonad:
+ def run_fn(state: EditorState) -> Result:
+ return func(sel, state)
+
+ return StateMonad(run_fn)
+
+ @wraps(func)
+ def wrapper(arg: Selection | StateMonad) -> StateMonad:
+ if isinstance(arg, StateMonad):
+ return arg.bind(make_monad)
+ else:
+ return make_monad(arg)
+
+ return wrapper
+
+
+def group(*exprs: StateMonad) -> StateMonad:
+ def run_grouped(state: EditorState) -> Result:
+ all_selections = []
+ all_changes = []
+
+ for expr in exprs:
+ sel, changes = expr.run(state)
+ all_selections.append(sel)
+ all_changes.extend(changes)
+
+ # TODO: Check for any overlapping changes
+ # TODO: Merge selections into union
+
+ return (
+ all_selections[0] if all_selections else state.current_selection,
+ all_changes,
+ )
+
+ return StateMonad(run_grouped)