Module grscheller.datastructures.functional.maybe

Module grscheller.datastructures.functional.maybe

Implemention of the Maybe Monad, sometimes called the Option ot Optional Monad.

Expand source code
# Copyright 2023 Geoffrey R. Scheller
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module grscheller.datastructures.functional.maybe

Implemention of the Maybe Monad, sometimes called the Option ot Optional Monad.
"""

from __future__ import annotations
from typing import Any, Callable

__all__ = ['Maybe', 'Some', 'Nothing']
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023 Geoffrey R. Scheller"
__license__ = "Appache License 2.0"

class Maybe():
    """Class representing a potentially missing value.

    - Implements the Option Monad
    - Maybe(value) constructs "Some(value)" 
    - Both Maybe() or Maybe(None) constructs a "Nothing"
    - immutable semantics - map & flatMap return modified copies
    - None is always treated as a non-existance value
      - cannot be stored in an object of type Maybe
      - semantically None does not exist
      - None only has any real existance as an implementration detail
    """
    def __init__(self, value: Any=None):
        self._value = value

    def __bool__(self) -> bool:
        """Return false if a Nothing, otherwise true."""
        return self._value != None

    def __len__(self) -> int:
        """A Maybe either contains something or not.
        Return 1 if a Some, 0 if a Nothing.
        """
        if self:
            return 1
        return 0

    def __iter__(self):
        """Yields its value if not a Nothing"""
        if self:
            yield self._value

    def __repr__(self) -> str:
        if self:
            return 'Some(' + repr(self._value) + ')'
        else:
            return 'Nothing'

    def __eq__(self, other: Maybe) -> bool:
        """Returns true if both sides are Nothings, or if both sides are Somes
        contining values which compare as equal.
        """
        if not isinstance(other, type(self)):
            return False
        return self._value == other._value

    def map(self, f: Callable[[Any], Any]) -> Maybe:
        if self:
            return Maybe(f(self._value))
        else:
            return Maybe()

    def flatMap(self, f: Callable[[Any], Maybe]) -> Maybe:
        if self:
            return f(self._value)
        else:
            return Maybe()

    def get(self) -> Any | None:
        """Get contents if they exist, otherwise return None. Caller is
        responsible with dealing with a None return value.
        """
        return self._value

    def getOrElse(self, default: Any) -> Any:
        """Get contents if they exist, otherwise return provided default value,
        which is guarnteed never to be None. If the caller sets it to None,
        swap it for the empty tuple (). () was choosen since it is iterable and
        "does the right thing" in an iterable context. If caller really wants
        None returned, they can use the get() method instead.
        """
        if default is None:
            default = ()
        if self:
            return self._value
        else:
            return default

# Maybe convenience functions/objects. Like "unit", "Nil", "()" in FP-languages.
# These are not necessary to ues Maybe, but gives Maybe the flavor of a Union
# type without using either inheritance or unnecessary internal state.

def Some(value=None):
    """Function for creating a Maybe from a value. If value is None or missing,
    returns a Nothing.
    """
    return Maybe(value)

Nothing = Some()  # Nothing is not a singleton, test via equality not identity.

if __name__ == "__main__":
    pass

Functions

def Some(value=None)

Function for creating a Maybe from a value. If value is None or missing, returns a Nothing.

Expand source code
def Some(value=None):
    """Function for creating a Maybe from a value. If value is None or missing,
    returns a Nothing.
    """
    return Maybe(value)

Classes

class Maybe (value: Any = None)

Class representing a potentially missing value.

  • Implements the Option Monad
  • Maybe(value) constructs "Some(value)"
  • Both Maybe() or Maybe(None) constructs a "Nothing"
  • immutable semantics - map & flatMap return modified copies
  • None is always treated as a non-existance value
  • cannot be stored in an object of type Maybe
  • semantically None does not exist
  • None only has any real existance as an implementration detail
Expand source code
class Maybe():
    """Class representing a potentially missing value.

    - Implements the Option Monad
    - Maybe(value) constructs "Some(value)" 
    - Both Maybe() or Maybe(None) constructs a "Nothing"
    - immutable semantics - map & flatMap return modified copies
    - None is always treated as a non-existance value
      - cannot be stored in an object of type Maybe
      - semantically None does not exist
      - None only has any real existance as an implementration detail
    """
    def __init__(self, value: Any=None):
        self._value = value

    def __bool__(self) -> bool:
        """Return false if a Nothing, otherwise true."""
        return self._value != None

    def __len__(self) -> int:
        """A Maybe either contains something or not.
        Return 1 if a Some, 0 if a Nothing.
        """
        if self:
            return 1
        return 0

    def __iter__(self):
        """Yields its value if not a Nothing"""
        if self:
            yield self._value

    def __repr__(self) -> str:
        if self:
            return 'Some(' + repr(self._value) + ')'
        else:
            return 'Nothing'

    def __eq__(self, other: Maybe) -> bool:
        """Returns true if both sides are Nothings, or if both sides are Somes
        contining values which compare as equal.
        """
        if not isinstance(other, type(self)):
            return False
        return self._value == other._value

    def map(self, f: Callable[[Any], Any]) -> Maybe:
        if self:
            return Maybe(f(self._value))
        else:
            return Maybe()

    def flatMap(self, f: Callable[[Any], Maybe]) -> Maybe:
        if self:
            return f(self._value)
        else:
            return Maybe()

    def get(self) -> Any | None:
        """Get contents if they exist, otherwise return None. Caller is
        responsible with dealing with a None return value.
        """
        return self._value

    def getOrElse(self, default: Any) -> Any:
        """Get contents if they exist, otherwise return provided default value,
        which is guarnteed never to be None. If the caller sets it to None,
        swap it for the empty tuple (). () was choosen since it is iterable and
        "does the right thing" in an iterable context. If caller really wants
        None returned, they can use the get() method instead.
        """
        if default is None:
            default = ()
        if self:
            return self._value
        else:
            return default

Methods

def flatMap(self, f: Callable[[Any], Maybe]) ‑> Maybe
Expand source code
def flatMap(self, f: Callable[[Any], Maybe]) -> Maybe:
    if self:
        return f(self._value)
    else:
        return Maybe()
def get(self) ‑> Optional[Any]

Get contents if they exist, otherwise return None. Caller is responsible with dealing with a None return value.

Expand source code
def get(self) -> Any | None:
    """Get contents if they exist, otherwise return None. Caller is
    responsible with dealing with a None return value.
    """
    return self._value
def getOrElse(self, default: Any) ‑> Any

Get contents if they exist, otherwise return provided default value, which is guarnteed never to be None. If the caller sets it to None, swap it for the empty tuple (). () was choosen since it is iterable and "does the right thing" in an iterable context. If caller really wants None returned, they can use the get() method instead.

Expand source code
def getOrElse(self, default: Any) -> Any:
    """Get contents if they exist, otherwise return provided default value,
    which is guarnteed never to be None. If the caller sets it to None,
    swap it for the empty tuple (). () was choosen since it is iterable and
    "does the right thing" in an iterable context. If caller really wants
    None returned, they can use the get() method instead.
    """
    if default is None:
        default = ()
    if self:
        return self._value
    else:
        return default
def map(self, f: Callable[[Any], Any]) ‑> Maybe
Expand source code
def map(self, f: Callable[[Any], Any]) -> Maybe:
    if self:
        return Maybe(f(self._value))
    else:
        return Maybe()