These are chat archives for evhub/coconut

18th
Jun 2018
balajeerc
@balajeerc
Jun 18 2018 02:23
Hi, I am trying to implement the equivalent of the Either monad. In the following code, I want to be able to skip/bypass execution of sqrt and go directly to the handle function:
from math import sqrt

# A simple unsafe function emulation
# Throws an error if input isn't even
def throw_on_odd(num):
    """Test function that throws on error"""
    if num % 2 == 0:
        return num / 2
    raise ValueError("Input must be even")


# Either definition
data Left(error_msg)
data Right(result)
Either = (Left, Right)

# Wrapper function to Wrap all exception trhowing
# functions and return an Either type
def wrap_unsafe(error_throwing_function):
    def wrapper(*args):
        try:
            return Right(error_throwing_function(*args))
        except Exception as err:
            return Left(err)
    return wrapper

# Wrapping the unsafe throw_on_odd function
wrapped = wrap_unsafe(throw_on_odd)

def safe_halve(input) = wrapped(input)

def handle(Left(error)):
    "Ran into error: " + str(error) |> print
@addpattern(handle)
def handle(Right(val)):
    "Found value: " + str(val) |> print

# Objective: How to get the output of safe_halve
# to bypass sqrt in case the return value is Left type
safe_halve(4) |> sqrt |> handle
safe_halve(5) |> sqrt |> handle
balajeerc
@balajeerc
Jun 18 2018 02:35
So far, the only workaround I've found to achieve what I want is to define another wrapper call_safe:
def call_safe(func, Right(val)) = Right(func(val))
@addpattern(call_safe)
def call_safe(func, Left(err)) = Left(err)

# Objective: How to get the output of safe_halve
# to bypass sqrt in case the return value is Left type
safe_halve(4) |> call_safe$(sqrt) |> handle
safe_halve(5) |> call_safe$(sqrt) |> handle
However, I find this a bit tedious since I have to wrap every downstream function with call_safe.
Is there a more elegant way to do this?
'jamin
@acdimalev
Jun 18 2018 17:19
@balajeerc what you have seems about right. Your call_safe function is effectively emulating bind in other FP languages (join . map). Coconut does have a map implementation (fmap) that you could use (to effectively implement Either A -> Either Either B by wrapping a A -> Either B function with fmap), but I don't believe Coconut has an implementation for join.
'jamin
@acdimalev
Jun 18 2018 17:58
A different way to look at this... even without the join part, the pipeline operators are still intended to be used with fmap for performing map operations (e.g. [1, 2, 3] |> fmap$(str)). Even with a join function of some sort to implement bind, I would expect the code to be safe_halve(4) |> bind$(sqrt) |> handle. Since this effectively desugars down to handle(bind(sqrt, safe_halve(4))).
Elliott Indiran
@eindiran
Jun 18 2018 22:07
@balajeerc Did you recently come across this post? https://fsharpforfunandprofit.com/rop/ I just saw this today, so wondered if you saw it as well
Elliott Indiran
@eindiran
Jun 18 2018 22:21

An approach you might consider would be using a decorator to wrap all functions in the chain if you want to skip them. This at least keeps all of the calls near the function definitions:

from functools import wraps
def skip_on_error(fn):
    @wraps(fn)
    def skip_error_path(input_data: Either):
        case input_data:
            match Left(val):
                return input_data
            match Right(val):
                return fn(input_data)
        else:
            return Left("Did not receive an Either wrapped item.")
    return skip_error_path


def first_function(n):
    if ...
        return Right(n)
   else:
        return Left("There was a problem in first_function()")


@skip_on_error
def second_fn(n):
    ...


@skip_on_error
def third_fn(n):
...


def handle(n):
      """Always do this."""
    ...

some_value |> first_function |> second_function |> third_fn |> handle

All the functions you decorated are skipped in the pipeline whenever an object that is Left wrapped passes through, until it arrives at handle().

This has the downside of requiring that you make another function to do the square root calculation besides sqrt().
Elliott Indiran
@eindiran
Jun 18 2018 22:30
In any case, if you haven't read the article, check it out, as there is some very relevant stuff in there.