Closed austinwarner-8451 closed 17 hours ago
A couple thoughts:
Evaluatable[Callable]
as it's argument would be called apply
For this reason I'm leaning towards apply for the name. It's more intuitive for Python programmers, and still technically works in the same way that you'd expect if you were coming from a pure functional language that uses Monads for everything
Adding a .bind
method as well. Bind takes a function of type A -> Evaluatable[B]
and returns a new Evaluatable[B]
. This is useful for when the evaluation is dependent on the result of a different dataset/options
Adding a
.bind
method as well. Bind takes a function of typeA -> Evaluatable[B]
and returns a newEvaluatable[B]
. This is useful for when the evaluation is dependent on the result of a different dataset/options
The motivation for this is two-fold
Iteration
s in #4 Also realizing that Switch
object could (and arguable should) be expressed in terms of .bind
The following are equivalent
@dataset
def foo():
...
@dataset
def bar():
...
Switch(
Option('X'),
{
True: foo,
False: bar,
},
None
)
Option('X').bind(lambda x: {True: foo, False: bar}.get(x, Value(None)))
Not proposing that Switch
should be abandoned for this syntax, as the current syntax is much more concise. However, it could be beneficial to recreate Switch like so
MaybeEvaluatable = Union[T, Evaluatable[T]]
def _ensure_evaluatable(x: MaybeEvaluatable[T]) -> Evaluatable[T]:
return Value(val) if not isinstance(val, Evaluatable) else val
def switch(
evaluatable: Evaluatable[A],
lookup: Dict[A, MaybeEvaluatable[B]],
default: Optional[MaybeEvaluatable[B]]
) -> Evaluatable[B]:
return evaluatable.bind(lambda x: _ensure_evaluatable(lookup.get(x, default)))
If we add switch
as a function like this we could deprecate Switch
the class
This would also allow us to add a more general case
function as well
def case(
evaluatable: Evaluatable[A],
*cases: Tuple[Callable[[A], bool], MaybeEvaluatable[B]]
) -> Evaluatable[B]:
def _case(a: A) -> Evaluatable[B]:
for predicate, target in cases:
if predicate(a):
return _ensure_evaluatable(target)
return evaluatable.bind(_case)
Wait I'm an idiot we can't add new methods to Evaluatable
because it is a breaking change.
We can add new Apply
and Bind
types that do this, it just means we cant chain everything together like before.
This is coming in Labrea 2.0
Sometimes we just want to perform simple transformations of Evaluatables, and creating a new dataset can be cumbersome. The proposal is to add a .map method that returns a new Evaluatable which is equivalent to evaluating the original Evaluatable then applying the function.
The use of the name .map comes from pure functional languages and Monads, but may be confusing to those unfamiliar with the theory since map is used for iterables in Python. We could alternatively call it .apply, which has a similar but different meaning in most pure functional languages, but would be clearer for those only familiar with Python.