Module grscheller.datastructures.core.fp

Module grscheller.datastructures.core.functional

Class Maybe: Implements the Maybe Monad, also called Option or Optional Monad. Class Either: Implements a left biased Either 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.core.functional

Class Maybe: Implements the Maybe Monad, also called Option or Optional Monad.
Class Either: Implements a left biased Either Monad.
"""

from __future__ import annotations

__all__ = [
            'FP', 'FP_rev',
            'Either', 'Left', 'Right',
            'Maybe', 'Some', 'Nothing',
            'maybeToEither', 'eitherToMaybe'
          ]
__author__ = "Geoffrey R. Scheller"
__copyright__ = "Copyright (c) 2023 Geoffrey R. Scheller"
__license__ = "Appache License 2.0"

from typing import Any, Callable
from itertools import chain
from .iterlib import exhaust, merge

class FP():
    def __init__(self):
        pass

    # For FIFO type data structures.
    def map(self, f: Callable[[Any], Any]) -> type[FP]:
        return type(self)(*map(f, self))

    def flatMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*chain(*map(iter, map(f, self))))

    def mergeMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*merge(*map(iter, map(f, self))))

    def exhaustMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*exhaust(*map(iter, map(f, self))))

class FP_rev(FP):
    def __reversed__(self):
        raise NotImplementedError

    # FP modified for FILO type data structures.
    def map(self, f: Callable[[Any], Any]) -> type[FP_rev]:
        # return type(self)(*map(f, reversed(self)))
        return type(self)(*map(f, reversed(self)))

    def flatMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
        return type(self)(*chain(*map(reversed, map(f, reversed(self)))))

    def mergeMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
        return type(self)(*merge(*map(reversed, map(f, reversed(self)))))

    def exhaustMap(self, f: Callable[[Any], type[FP]]) -> type[FP_rev]:
        return type(self)(*exhaust(*map(reversed, map(f, reversed(self)))))

class Maybe(FP):
    """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 __iter__(self):
        """Yields its value if not a Nothing"""
        if self:
            yield self._value

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

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

    def __len__(self) -> int:
        """A Maybe either contains something or not.

        Return 1 if a Some
        Return 0 if a Nothing
        """
        if self:
            return 1
        else:
            return 0

    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 get(self, default: Any=None) -> Any:
        """Get contents if they exist, otherwise return None. Caller is
        responsible with dealing with a None return value.
        """
        if self:
            return self._value
        else:
            return default

# Maybe convenience functions/objects.

def maybeToEither(m: Maybe, right: Any=None) -> Either:
    """Convert a Maybe to an Either"""
    return Either(m.get(), right)

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

#: Maybe object representing a missing value,
#: Nothing is not a singleton. Test via equality not identity.
Nothing = Some()


class Either(FP):
    """Class that either contains a Left value or Right value, but not both. If
    right not given, set it to the empty str. This version is biased to the
    Left, which is intended as the "happy path."
    """
    def __init__(self, left: Any=None, right: Any=None):
        if right is None:
            right = ''
        if left == None:
            self._isLeft = False
            self._value = right
        else:
            self._isLeft = True
            self._value = left

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

    def __repr__(self):
        if self:
            return 'Left(' + repr(self._value) + ')'
        else:
            return 'Right(' + repr(self._value) + ')'

    def __bool__(self) -> bool:
        """Return true if a Left, false if a Right"""
        return self._isLeft

    def __len__(self) -> int:
        """An Either always contains just one thing, which is not None"""
        return 1

    def __eq__(self, other: Either) -> bool:
        """True if both sides are same "type" and values compare as equal"""
        if not isinstance(other, type(self)):
            return False
        if (self and other) or (not self and not other):
            return self._value == other._value
        return False

    def get(self, default: Any=None) -> Any:
        """get value if a Left, otherwise return default value."""
        if self:
            return self._value
        return default

    def map(self, f: Callable[[Any], Any], right=None) -> Either:
        if self:
            return Either(f(self._value), right)
        return self

    def mapRight(self, g: Callable[[Any], Any]) -> Either:
        """Map over if a Right."""
        if self:
            return self
        return Right(g(self._value))

    def flatMap(self, f: Callable[[Any], Either], right=None) -> Either:
        """flatMap with a Right default. Replace Right with right"""
        if self:
            if right is None:
                return f(self._value)
            else:
                return f(self._value).mapRight(lambda _: right)
        else:
            if right is None:
                return self
            else:
                return self.mapRight(lambda _: right)

    def mergeMap(self, f: Callable[[Any], Either], right=None) -> Either:
        """flatMap with a Right default. Merge right into Right via +"""
        if self:
            if right is None:
                return f(self._value)
            else:
                return f(self._value).mapRight(lambda x: x + right)
        else:
            if right is None:
                return self
            else:
                return self.mapRight(lambda x: x + right)

# Either convenience functions, act like subtype constructors.

def eitherToMaybe(e: Either) -> Maybe:
    """Convert an Either to a Maybe"""
    return Maybe(e.get())

def Left(left: Any, right: Any=None) -> Either:
    """Function returns Left Either if left != None, otherwise Right Either."""
    return Either(left, right)

def Right(right: Any) -> Either:
    """Function to construct a Right Either."""
    return Either(None, right)

if __name__ == "__main__":
    pass

Global variables

var Nothing

Maybe object representing a missing value, Nothing is not a singleton. Test via equality not identity.

Functions

def Left(left: Any, right: Any = None) ‑> Either

Function returns Left Either if left != None, otherwise Right Either.

Expand source code
def Left(left: Any, right: Any=None) -> Either:
    """Function returns Left Either if left != None, otherwise Right Either."""
    return Either(left, right)
def Right(right: Any) ‑> Either

Function to construct a Right Either.

Expand source code
def Right(right: Any) -> Either:
    """Function to construct a Right Either."""
    return Either(None, right)
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)
def eitherToMaybe(e: Either) ‑> Maybe

Convert an Either to a Maybe

Expand source code
def eitherToMaybe(e: Either) -> Maybe:
    """Convert an Either to a Maybe"""
    return Maybe(e.get())
def maybeToEither(m: Maybe, right: Any = None) ‑> Either

Convert a Maybe to an Either

Expand source code
def maybeToEither(m: Maybe, right: Any=None) -> Either:
    """Convert a Maybe to an Either"""
    return Either(m.get(), right)

Classes

class Either (left: Any = None, right: Any = None)

Class that either contains a Left value or Right value, but not both. If right not given, set it to the empty str. This version is biased to the Left, which is intended as the "happy path."

Expand source code
class Either(FP):
    """Class that either contains a Left value or Right value, but not both. If
    right not given, set it to the empty str. This version is biased to the
    Left, which is intended as the "happy path."
    """
    def __init__(self, left: Any=None, right: Any=None):
        if right is None:
            right = ''
        if left == None:
            self._isLeft = False
            self._value = right
        else:
            self._isLeft = True
            self._value = left

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

    def __repr__(self):
        if self:
            return 'Left(' + repr(self._value) + ')'
        else:
            return 'Right(' + repr(self._value) + ')'

    def __bool__(self) -> bool:
        """Return true if a Left, false if a Right"""
        return self._isLeft

    def __len__(self) -> int:
        """An Either always contains just one thing, which is not None"""
        return 1

    def __eq__(self, other: Either) -> bool:
        """True if both sides are same "type" and values compare as equal"""
        if not isinstance(other, type(self)):
            return False
        if (self and other) or (not self and not other):
            return self._value == other._value
        return False

    def get(self, default: Any=None) -> Any:
        """get value if a Left, otherwise return default value."""
        if self:
            return self._value
        return default

    def map(self, f: Callable[[Any], Any], right=None) -> Either:
        if self:
            return Either(f(self._value), right)
        return self

    def mapRight(self, g: Callable[[Any], Any]) -> Either:
        """Map over if a Right."""
        if self:
            return self
        return Right(g(self._value))

    def flatMap(self, f: Callable[[Any], Either], right=None) -> Either:
        """flatMap with a Right default. Replace Right with right"""
        if self:
            if right is None:
                return f(self._value)
            else:
                return f(self._value).mapRight(lambda _: right)
        else:
            if right is None:
                return self
            else:
                return self.mapRight(lambda _: right)

    def mergeMap(self, f: Callable[[Any], Either], right=None) -> Either:
        """flatMap with a Right default. Merge right into Right via +"""
        if self:
            if right is None:
                return f(self._value)
            else:
                return f(self._value).mapRight(lambda x: x + right)
        else:
            if right is None:
                return self
            else:
                return self.mapRight(lambda x: x + right)

Ancestors

Methods

def flatMap(self, f: Callable[[Any], Either], right=None) ‑> Either

flatMap with a Right default. Replace Right with right

Expand source code
def flatMap(self, f: Callable[[Any], Either], right=None) -> Either:
    """flatMap with a Right default. Replace Right with right"""
    if self:
        if right is None:
            return f(self._value)
        else:
            return f(self._value).mapRight(lambda _: right)
    else:
        if right is None:
            return self
        else:
            return self.mapRight(lambda _: right)
def get(self, default: Any = None) ‑> Any

get value if a Left, otherwise return default value.

Expand source code
def get(self, default: Any=None) -> Any:
    """get value if a Left, otherwise return default value."""
    if self:
        return self._value
    return default
def map(self, f: Callable[[Any], Any], right=None) ‑> Either
Expand source code
def map(self, f: Callable[[Any], Any], right=None) -> Either:
    if self:
        return Either(f(self._value), right)
    return self
def mapRight(self, g: Callable[[Any], Any]) ‑> Either

Map over if a Right.

Expand source code
def mapRight(self, g: Callable[[Any], Any]) -> Either:
    """Map over if a Right."""
    if self:
        return self
    return Right(g(self._value))
def mergeMap(self, f: Callable[[Any], Either], right=None) ‑> Either

flatMap with a Right default. Merge right into Right via +

Expand source code
def mergeMap(self, f: Callable[[Any], Either], right=None) -> Either:
    """flatMap with a Right default. Merge right into Right via +"""
    if self:
        if right is None:
            return f(self._value)
        else:
            return f(self._value).mapRight(lambda x: x + right)
    else:
        if right is None:
            return self
        else:
            return self.mapRight(lambda x: x + right)
class FP
Expand source code
class FP():
    def __init__(self):
        pass

    # For FIFO type data structures.
    def map(self, f: Callable[[Any], Any]) -> type[FP]:
        return type(self)(*map(f, self))

    def flatMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*chain(*map(iter, map(f, self))))

    def mergeMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*merge(*map(iter, map(f, self))))

    def exhaustMap(self, f: Callable[[Any], FP]) -> type[FP]:
        return type(self)(*exhaust(*map(iter, map(f, self))))

Subclasses

Methods

def exhaustMap(self, f: Callable[[Any], FP]) ‑> type[FP]
Expand source code
def exhaustMap(self, f: Callable[[Any], FP]) -> type[FP]:
    return type(self)(*exhaust(*map(iter, map(f, self))))
def flatMap(self, f: Callable[[Any], FP]) ‑> type[FP]
Expand source code
def flatMap(self, f: Callable[[Any], FP]) -> type[FP]:
    return type(self)(*chain(*map(iter, map(f, self))))
def map(self, f: Callable[[Any], Any]) ‑> type[FP]
Expand source code
def map(self, f: Callable[[Any], Any]) -> type[FP]:
    return type(self)(*map(f, self))
def mergeMap(self, f: Callable[[Any], FP]) ‑> type[FP]
Expand source code
def mergeMap(self, f: Callable[[Any], FP]) -> type[FP]:
    return type(self)(*merge(*map(iter, map(f, self))))
class FP_rev
Expand source code
class FP_rev(FP):
    def __reversed__(self):
        raise NotImplementedError

    # FP modified for FILO type data structures.
    def map(self, f: Callable[[Any], Any]) -> type[FP_rev]:
        # return type(self)(*map(f, reversed(self)))
        return type(self)(*map(f, reversed(self)))

    def flatMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
        return type(self)(*chain(*map(reversed, map(f, reversed(self)))))

    def mergeMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
        return type(self)(*merge(*map(reversed, map(f, reversed(self)))))

    def exhaustMap(self, f: Callable[[Any], type[FP]]) -> type[FP_rev]:
        return type(self)(*exhaust(*map(reversed, map(f, reversed(self)))))

Ancestors

Subclasses

Methods

def exhaustMap(self, f: Callable[[Any], type[FP]]) ‑> type[FP_rev]
Expand source code
def exhaustMap(self, f: Callable[[Any], type[FP]]) -> type[FP_rev]:
    return type(self)(*exhaust(*map(reversed, map(f, reversed(self)))))
def flatMap(self, f: Callable[[Any], type[FP_rev]]) ‑> type[FP_rev]
Expand source code
def flatMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
    return type(self)(*chain(*map(reversed, map(f, reversed(self)))))
def map(self, f: Callable[[Any], Any]) ‑> type[FP_rev]
Expand source code
def map(self, f: Callable[[Any], Any]) -> type[FP_rev]:
    # return type(self)(*map(f, reversed(self)))
    return type(self)(*map(f, reversed(self)))
def mergeMap(self, f: Callable[[Any], type[FP_rev]]) ‑> type[FP_rev]
Expand source code
def mergeMap(self, f: Callable[[Any], type[FP_rev]]) -> type[FP_rev]:
    return type(self)(*merge(*map(reversed, map(f, reversed(self)))))
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(FP):
    """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 __iter__(self):
        """Yields its value if not a Nothing"""
        if self:
            yield self._value

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

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

    def __len__(self) -> int:
        """A Maybe either contains something or not.

        Return 1 if a Some
        Return 0 if a Nothing
        """
        if self:
            return 1
        else:
            return 0

    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 get(self, default: Any=None) -> Any:
        """Get contents if they exist, otherwise return None. Caller is
        responsible with dealing with a None return value.
        """
        if self:
            return self._value
        else:
            return default

Ancestors

Methods

def get(self, default: Any = None) ‑> 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, default: Any=None) -> Any:
    """Get contents if they exist, otherwise return None. Caller is
    responsible with dealing with a None return value.
    """
    if self:
        return self._value
    else:
        return default