Functional Programming for OOP Developers: Part 2 – No Errors and Getting Things

Introduction

In my journey to learn functional programming and drink deep of the kool-aid, I wanted to share my latest learnings. Specifically around the quest for no errors and how you get things.

I’ve also just recently applied these same concepts in Python, not just JavaScript, so I’ll mix and match the examples to show how the concepts are universal.

After reading this article, you should understand why errors aren’t helpful embedded in your code & avoiding them is good and why we use functions to get things instead of the old way of assigning variables. Check out the first article if you missed it.

Recap of Pure Functions

If you’ve read my previous article, and hopefully Eric Elliot’s Pure Function one as well, you should know what pure functions are, and why are they are important in functional programming.

If not, a quick recap. Pure functions are functions that always give the same output given the same input.

function alwaysTrue()
{
return true;
}

The function alwaysTrue will always return true no matter what you pass it, or don’t, or what order you call that function amongst other functions.

console.log(alwaysTrue()); // true
console.log(alwaysTrue('cow')); // true

This function is not pure:

function addStuff(a, b)
{
return a + b + data;
}

If we call it with 1 and 1, we get an error:

var result = addStuff(1, 1); // goes boom

If we set data to 1, it gives us 3:

var data = 1;
var result = addStuff(1, 1); // 3

If we change data to 2, we get 4:

var data = 2;
var result = addStuff(1, 1); // 4

Finally, if we change data after we call it, and call it again, we get different results from both function calls:

var data = 1;
var result1 = addStuff(1, 1); // 3
data = 2;
var result2 = addStuff(1, 1); // 4

An impure function is not predictable, cannot be re-used without dragging a bunch of other variables and functions that make sure all the state is correct, and cannot be combined with other functions reliably because we don’t know how it will react.

Programming is supposed to be fun, so creating pure functions ensures what you make actually works and allows you to make bigger software programs that run (somewhat) predictably.

The Quest for No Errors

As you’ve seen above, errors make a function not pure. Some languages attempt to make this pure. Java has throwable and throws to ensure the code only compiles of the caller has ensured all possible error types are handled. Python, while dynamic, still has this philosophy of catching specific types of errors first, then unknowns after. These don’t catch all errors at compile time, however. Haskell uses Maybe to balance this with pure functions.

Runtimes errors arise from when you load data such as JSON from REST API’s, use 3rd party libraries that do not indicate they throw errors yet do, or you’re using dynamic languages which have little to no compiler help.

You have 2 choices: Know all potential errors, or don’t use errors.

I’ve talked in the past about not creating errors using exceptions, and instead indicating in an opt-in way of what went wrong for programmers consuming your code. The reason for this is usually all errors are unrecoverable. The best you can, and should, do is log it so you or others can see it and either know what’s going on, or debug the code and fix it later. Once you mix your code with other code, you’re part of the solution, not part of the problem.

If errors make the code less pure, then how do we not use errors?

How Do You Avoid Using Errors?

Let me demonstrate using the Amazon AWS Python API. They have an awesome API that’s mostly consistent. For Python, the library is called Boto3, and unlike JavaScript, Python is a blocking language so asynchronous code will stop on the line making asynchronous calls (like calling an AWS server) until it’s complete.

If you don’t know AWS, just know that an “EC2” or “ee-sea-two” is Amazon’s word for a “server”. You can easily create and destroy these things. You get instances of them, just like you get instances of an object in normal programming.

The following code searches my Amazon account for any servers that are owned by me. When I create new EC2’s, I tag them with metadata, in this case owner is my name. That way, if I have thousands of servers, I can easily find mine.

response = ec2.describe_instances(
Filters=[
{
'Tag': 'owner',
'Values': [
'jesterxl',
]
},
]
)

The above code isn’t pure. The Node API will follow the standard Node callback convention. The Boto3 Python API, in this case, either:

  1. works and your response is a dictionary with search result information (JavaScript Object)
  2. doesn’t work, and raises a Python Exception (JavaScript throw)
  3. doesn’t work, and response isn’t a dictionary (rare, depends on API call such as S3.head_bucket)

Let’s make this pure by defining a pure Python function:

from pydash import is_dict
def are_my_servers_there(ec2):
try:
response = ec2.describe_instances(
Filters=[
{
'Tag': 'owner',
'Values': [
'jesterxl',
]
},
]
)
return is_dict(response)
except Exception as e:
print("find_my_servers failed:", e)
return False
result = are_my_servers_there()
# result will always be True or False

This function always returns True or False. Here’s 2 example nose2 unit tests:

def test_works:
value = are_my_servers_there(ec2_mock_good)
self.assertTrue(value)
def test_fails:
value = are_my_servers_there(ec2_mock_bad)
self.assertFalse(value)

If the Python syntax is throwing you off, here is the equivalent JavaScript Node syntax for comparison:

const _ = require('lodash');
const areMyServersThere = (ec2, callback)=>
{
try
{
var params = {
Filters: [
{
Name: 'owner',
Values: [
'jesterxl',
]
}
]
};
ec2.describeInstances(params, (err, data)=>
{
if(err)
{
console.log("describeInstances failed in callback:", err);
callback(false);
return;
}
callback(_.isObject(data));
});
}
catch(boom)
{
console.log("describeInstances failed:", boom);
callback(false);
}
};
const result = findMyServers();
// result is always true or false

This function always calls the callback with true or false. And the Mocha/Chai tests:

it('should find my servers', (done)=>
{
areMyServersThere(ec2MockGood, (foundThem)=>
{
if(foundThem)
{
done();
}
else
{
done(new Error("Couldn't find them.");
}
});
});
it('should not find my servers if not there', (done)=>
{
areMyServersThere(ec2MockBad, (foundThem)=>
{
if(foundThem === false)
{
done();
}
else
{
done(new Error("Somehow succeeded when it shouldn't.");
}
});
});

If you search for Python code online, sometimes it will write except for a couple of types of Exceptions first, and then default to the generic one like I did last. This is often for clean up code in the case of database connections, file/folder shuffling, or networking handles. Low level stuff requires low-level error checking, heh!

Node, on the other hand, at least in API development still can’t escape try/catch. Inside of callbacks developers will parse data. Outside they’ll create it for requests. The call that accepts a callback may not have proper error handling internally and throws before it can call the callback. Global error handling is still a challenge, both in browsers and in Node.

The good news is not all functions you use throw. Some are pure by default (Lodash and Pydash for example). Some native API’s are predictable and only 1 or a few errors. Some are small predicates which you know won’t throw errors. In those cases, if you need to know about specific ones, you can handle those. How you go about that in a pure way, however, is up to you. As long as you call the same function with the same inputs and it produces the same outputs, then however you’re handling the exceptions is probably pure.

Things get fuzzy with I/O & databases. At that point you have to ask how pure do you want to make things? Easy answers are:

  • If it works, and deadline is approaching, then pure enough.
  • If it works, isn’t pure, and deadline is far away, then not pure enough.

Getting Things

Python and JavaScript throw errors when you attempt to access nested properties on an Object and misspell something, or the property simply isn’t there. This is lessened if you utilize a strongly typed language like TypeScript, and have good code editors that have intellisense and help you explore the Object before you type.

Property access is impure. Since you don’t have an ‘input’, there is no way to ensure you’re always getting the same result. This is used all the time in REST API’s where you get JSON back and start digging into the yummy data only to realize it’s missing things.

someRestCall()
.then((results)=>
{
// should be results.data.user
const user = results.user;
})
.catch((err)=>
{
// either we failed to make the call,
// or we failed to parse the data.
});

Another is lower-level API’s, such as detecting if you have the Performance API to detect browser refresh:

if(window.performance)
{
if(performance.navigation.type  == 1 )
{
console.log('page reloaded');
}
}

That code is all well in good in the latest browsers… except for Node where it throws an exception since she doesn’t have a window. You’ll run into this when you start using Babel extensions that were meant to only work for Node, but you want to test your browser code outside of Karma.

Another area is when you start combining functions and list comprehensions together for the above. Once you throw higher order functions together, while pure, you may not know exactly what you’re getting. This could just be too much cognitive load, you’re dealing with 3rd party impure code in the mix, or you’re dealing with I/O like asynchronous REST API’s. Even in this mostly pure world, you still can’t trust your Objects.

To state it clearly: Objects are not pure functions.

Lodash’ get is, though. Pydash has get as well.

In the above REST API, instead of accessing the response data impurely, we can use get:

const user = _.get(results, 'user');

… now when I first saw get, I thought it was stupid. I had no idea what pure functions were, nor was I using a lot of list comprehensions together. I’ve been accessing Objects for ages, why use a function?

Now you know why:

  1. _.get is pure
  2. it doesn’t throw errors
  3. You can default what you want so if it’s not there, you always get a black and white result: it’s either there, or it ain’t.

For example, single dots on things you know exist are ok:

var result = {
data: {
user: {
name: 'Jesse'
}
}
};
var user = result.user;
console.log("user:", user); // user: undefined

However, here we misspell data to dat, and it throws:

var result = {
data: {
user: {
name: 'Jesse'
}
}
};
var user = result.dat.user;
console.log("user:", user); // never run

Watch when happens when we use _.get:

var user = _.get(result, 'dat.user');
console.log("user:", user); // user: undefined

You can even delete result:

var user = _.get(undefined, 'dat.user');
console.log("user:", user); // user: undefined

Outside of pure functions in the mutable state world, they can still save you from uncaught exceptions!

Combine that with it’s 3rd parameter which is a default value, and you can get even more predictable results:

expect(_.get(undefined, 'dat.user', undefined).to.equal(undefined);
expect(_.get(result, 'data.user.name', 'chicken')).to.not.equal('chicken');

We’ll see more of get‘s 3rd parameter when we start composing functions in a future article.

Et tu, Array?

(Arrays are full of holes… get it? lol!)

Even Arrays, a type of Object in JavaScript, are impure when you access them. This is part of the reason you see functions in Lodash for head, last, and tail (Although, tail is more for those coming from functional backgrounds where the first Array element is t3h special, a la Erlang/Elixir).

Here’s a common error:

var list;
result = list[2];
console.log("result:", result); // never runs

Solve that by using get instead:

var list;
result = _.get(list, '2');
console.log("result:", result); // undefined

A side benefit is that get works with Objects and Arrays so is flexible in that you can pass either.

Conclusions

The get function allows us a pure way to access Objects & Arrays in JavaScript and Dictionaries & Lists in Python. With reasonable defaults, you can use this to build and compose pure functions instead of littering them with impure property access.

Once you start composing functions like we’ll be doing in the next article, you want to ensure the main ones are as pure as possible. Debugging composed pure functions can be hard at first, so getting it right in the beginning helps.

You’ve also seen how errors are just that: something to be avoided instead of embraced. They make your functions impure, and confuse those who were expecting specific return values from your functions.

You do not need to use try/catch everywhere. In your own code, you’ll know when there could be areas which are out of your control and you need to. If using 3rd party code, if you have the time, see at what points they fail intentionally through unit and integration tests. This ensures you only use try/catch in areas you either don’t know (yet) or are intentional like in Python’s Boto3 library.

One important part of errors that shouldn’t be missed, however, is intentional ones. As you start creating higher level functions like the checkers in my previous article, giving good feedback is important. The reason(s) why predicates return false, or why composed functions return an error result instead of success help mitigate debugging, and ensure those learning your API know what’s really going on. Anyone who has used both React and Angular knows how superior React’s error messaging is.

Verbose, helpful errors are great and those SHOULD be embraced. However, you don’t have to raise/throw them unless it truly is an error that you want developers to know about it early.

Leave a Reply

Your email address will not be published. Required fields are marked *