Last active
April 29, 2023 16:52
-
-
Save asukaminato0721/1bb2dbcdfc4fcba243f0eb9bc3dc0b0a to your computer and use it in GitHub Desktop.
https://composingprograms.com/pages/24-mutable-data.html#propagating-constraints rewritten in OOP style
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
from dataclasses import dataclass | |
from typing import Callable, List | |
from operator import attrgetter, add, sub, mul, truediv | |
def adder(a: Connector, b: Connector, c: Connector): | |
return TernaryConstraint(a, b, c, add, sub, sub) | |
def multiplier(a: Connector, b: Connector, c: Connector): | |
"""The constraint that a * b = c.""" | |
return TernaryConstraint(a, b, c, mul, truediv, truediv) | |
def converter(c: Connector, f: Connector): | |
"""Connect c to f with constraints to convert from Celsius to Fahrenheit.""" | |
u, v, w, x, y = [Connector() for _ in range(5)] | |
multiplier(c, w, u) | |
multiplier(v, x, u) | |
adder(v, y, f) | |
w.set_val("", 9) | |
x.set_val("", 5) | |
y.set_val("", 32) | |
class Connector: | |
"""A connector between constraints.""" | |
def __init__(self, name: str = None): | |
self.informant = None | |
self.constraints: List[TernaryConstraint] = [] | |
self.val = None | |
self.name = name | |
def set_val(self, source: str, value: float): | |
if self.val is None: | |
self.informant, self.val = source, value | |
if self.name is not None: | |
print(self.name, "=", value) | |
Connector.inform_all_except(source, attrgetter("new_val"), self.constraints) | |
elif self.val != value: | |
print("Contradiction detected:", self.val, "vs", value) | |
def forget(self, source): | |
if self.informant == source: | |
self.informant, self.val = None, None | |
if self.name is not None: | |
print(self.name, "is forgotten") | |
Connector.inform_all_except(source, attrgetter("forget"), self.constraints) | |
def has_val(self): | |
return self.val is not None | |
def connect(self, source): | |
self.constraints.append(source) | |
@staticmethod | |
def inform_all_except( | |
source: str, message: Callable, constraints: List[TernaryConstraint] | |
): | |
"""Inform all constraints of the message, except source.""" | |
for c in constraints: | |
if c != source: | |
message(c)() | |
@dataclass | |
class TernaryConstraint: | |
"""The constraint that ab(a,b)=c and ca(c,a)=b and cb(c,b) = a.""" | |
a: Connector | |
b: Connector | |
c: Connector | |
ab: Callable[[float, float], float] | |
ca: Callable[[float, float], float] | |
cb: Callable[[float, float], float] | |
def __post_init__(self): | |
for i in (self.a, self.b, self.c): | |
i.connect(self) | |
def new_val(self): | |
av, bv, cv = [connector.has_val() for connector in (self.a, self.b, self.c)] | |
if av and bv: | |
self.c.set_val(self, self.ab(self.a.val, self.b.val)) | |
elif av and cv: | |
self.b.set_val(self, self.ca(self.c.val, self.a.val)) | |
elif bv and cv: | |
self.a.set_val(self, self.cb(self.c.val, self.b.val)) | |
def forget(self): | |
for connector in (self.a, self.b, self.c): | |
connector.forget(self) | |
celsius = Connector("Celsius") | |
fahrenheit = Connector("Fahrenheit") | |
converter(celsius, fahrenheit) | |
celsius.set_val("user", 25) | |
fahrenheit.set_val("user", 212) | |
celsius.forget("user") | |
fahrenheit.set_val("user", 212) | |
fahrenheit.set_val("user", 100) | |
fahrenheit.forget("user") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment