Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
returning an error code
#3
I don't think returning an error code is necessarily a bad thing; it's up to the programmer to decide which error handling strategy they want to employ. I had to review some Ruby code the other day and they'd returned a string in the error case, rather than throwing an exception. I think it's fine for quite simple cases and AFAIK Go does error handling that way too. Of course, if you need to do a lot of computations in a sequence, any of which could fail, then this simple strategy doesn't scale because the code becomes cluttered with all the if/else checks you have to do.

An alternative to exceptions is to use a result type (or "result monad") that represents a successful computation, or a failure. These can be chained together, resulting in code that's quite flat and readable and it's up to you when you actually do something with the failure. This is the functional approach, since in FP, you don't use exceptions as functions are pure and just return values. In Rust, it's called a Result, Scala has two types :Try and Either (my codebase at work actually uses both for whatever reason) and Kotlin has different library implementations (one of which is Nat Pryce's Result4k, from which I nicked the idea of the result_from function you see below).

Here are some test cases showing the idea, with an implementation following (note the code isn't meant for production; it's literally just for demo purposes. There are likely more fully featured implementations out there).

import unittest
 
from result import *
 
class TestResult(unittest.TestCase):
    def test_a_computation_that_completes_is_a_success(self):
        def a_computation_that_runs_successfully():
            x = 5
            return x * 10
 
        self.assertEqual(
            result_from(a_computation_that_runs_successfully),
            Success(50)
        )

    def test_a_computation_that_throws_an_exception_is_a_failure(self):
        def a_computation_that_fails():
            return int("foobar")
 
        self.assertEqual(
            result_from(a_computation_that_fails),
            Failure("invalid literal for int() with base 10: 'foobar'")
        )

    def test_composing_computations_runs_them_in_succession(self):
        self.assertEqual(
            Success(4)
                .flatmap(lambda x: Success(x + 1))
                .flatmap(lambda x: Success(x * 2)),
            Success(10)
        )

    def test_no_computation_is_carried_out_after_a_failure(self):
        self.assertEqual(
            Success(4)
                .flatmap(lambda x: Success(x + 1))
                .flatmap(lambda x: Failure("something bad happened"))
                .flatmap(lambda x: Success(x * 2)),
            Failure("something bad happened")
        )
        
    def test_a_success_can_be_transformed_into_another(self):
        self.assertEqual(
            Success(4).map(lambda x: x + 1),
            Success(5)
        )
 
    def test_transforming_a_failure_leaves_it_unchanged(self):
        self.assertEqual(
            Failure("something bad happened").map(lambda x: x + 1),
            Failure("something bad happened")
        )
   
if __name__ == '__main__':
    unittest.main(verbosity=2)
class Result(object):
    pass

class Success(Result):
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        return self._value

    def map(self, f):
        return Success(f(self.value))

    def flatmap(self, f):
        return f(self.value)

    def __eq__(self, other):
        return self.value == other.value

class Failure(Result):
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        return self._value

    def map(self, f):
        return Failure(self.value)

    def flatmap(self, f):
        return Failure(self.value)
    
    def __eq__(self, other):
        return self.value == other.value

def result_from(f):
    try:
        return Success(f())
    except Exception as e:
        return Failure(str(e))
I've kept the name of the method that lets you chain computations as flatmap as that's what it's called in the functional programming literature, since it really is analogous to map, but gets rid of any nesting (if you were to map, you'd end up with a Result inside another of course). Having said that, I did see a blog post where they'd called it andThen in a particular Kotlin implementation, because that made it more readable.

A Result is just a value, so at the point you need to know whether the computation succeeded or failed, you just check what kind of result you have:

result = some_computation_that_either_succeeds_or_fails()

if isinstance(result, Success):
    print(f"It succeeded and the result was: {result.value}")
else:
    print(f"It failed and the error was: {result.value}")
Other languages have pattern matching or otherwise more powerful versions of switch that make this look nicer, but the idea is the same.
Gribouillis likes this post
Reply


Messages In This Thread
returning an error code - by Skaperen - Feb-08-2021, 06:55 PM
RE: returning an error code - by Gribouillis - Feb-09-2021, 07:11 AM
RE: returning an error code - by ndc85430 - Feb-28-2021, 02:54 PM
RE: returning an error code - by Skaperen - Mar-01-2021, 02:21 AM
RE: returning an error code - by Gribouillis - Mar-01-2021, 08:06 AM
RE: returning an error code - by Skaperen - Mar-02-2021, 12:30 AM
RE: returning an error code - by Gribouillis - Mar-02-2021, 09:33 AM
RE: returning an error code - by Skaperen - Mar-02-2021, 06:49 PM
RE: returning an error code - by ndc85430 - Mar-03-2021, 07:05 AM

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020