From 85a1b13ff37d97f4f9a5e25d6313f57c3c7973b3 Mon Sep 17 00:00:00 2001 From: Alexis Maya-Isabelle Shuping Date: Wed, 25 Sep 2024 18:08:50 -0500 Subject: [PATCH] Add Option casting with tests; modify Variable parameters to use it. --- modules/data/Variable.py | 10 +++++----- modules/types/Option.py | 28 +++++++++++++++++++++++++++- test/types/test_Option.py | 8 ++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/modules/data/Variable.py b/modules/data/Variable.py index 7eeb818..a4a491a 100644 --- a/modules/data/Variable.py +++ b/modules/data/Variable.py @@ -106,9 +106,9 @@ class BoundVariable[A](Variable): ''' def __init__(self, name: str, val: A, var_type: Option[type] = Nothing(), desc: Option[str] = Nothing(), default: Option[A] = Nothing()): self.__name = name - self.__val = val - self.__desc = desc - self.__default = default + self.__val = Option.make_from(val) + self.__desc = Option.make_from(desc) + self.__default = Option.make_from(default) if var_type == Nothing(): if default == Nothing(): @@ -185,8 +185,8 @@ class UnboundVariable[A](Variable): ''' def __init__(self, name: str, var_type: Option[type] = Nothing(), desc: Option[str] = Nothing(), default: Option[A] = Nothing()): self.__name = name - self.__desc = desc - self.__default = default + self.__desc = Option.make_from(desc) + self.__default = Option.make_from(default) if var_type == Nothing(): if default != Nothing(): diff --git a/modules/types/Option.py b/modules/types/Option.py index 926d6d1..fcb0a54 100644 --- a/modules/types/Option.py +++ b/modules/types/Option.py @@ -70,6 +70,18 @@ def flat_map(self, f: Callable[[A], 'Option[B]']) -> 'Option[B]': ''' ... # pragma: no cover + @staticmethod + def make_from(obj: any) -> 'Option[A]': + ''' Convenience method to ensure an object is an Option. If `obj` is + already an Option, it is returned as-is. If `obj` is None, it is + converted to `Nothing()`. Otherwise, it is converted to `Some(obj)` + + :param obj: The object to encapsulate + :returns: `obj` if `obj` is an Option; otherwise, `Some(obj)` if obj + is not None; otherwise, `Nothing()`. + ''' + ... # pragma: no cover + def __eq__(self, other): if not issubclass(type(other), Option): return False @@ -123,4 +135,18 @@ def flat_map(self, f: Callable[[A], Option[B]]) -> Option[B]: return f(self.__val) def __str__(self): - return f'Some[{self.val.__class__.__name__}]({self.val})' \ No newline at end of file + return f'Some[{self.val.__class__.__name__}]({self.val})' + + +def _make_from(obj: any) -> Option[A]: + # The inner function has to be defined after `Some` and `Nothing`, and then + # injected into the Object class. + if issubclass(type(obj), Option): + return obj + + if obj is None: + return Nothing() + + return Some(obj) + +Option.make_from = _make_from \ No newline at end of file diff --git a/test/types/test_Option.py b/test/types/test_Option.py index af05d47..d1317e1 100644 --- a/test/types/test_Option.py +++ b/test/types/test_Option.py @@ -123,3 +123,11 @@ def tf_stringify(x: float) -> str: .flat_map(tf_invert) \ .map(tf_stringify) == Nothing(), \ "`map` should be skipped, since `tf_invert` returns Nothing()." + +def test_make_from(): + assert Option.make_from(1) == Some(1) + assert Option.make_from(Some(1)) == Some(1) + + assert Option.make_from(Nothing()) == Nothing() + assert Option.make_from(None) == Nothing() + assert Option.make_from(Some(None)) == Some(None) \ No newline at end of file