9

I am building a website using erlang, mnesia, and webmachine. Most of the documentation I have read praises the virtues of having referentially transparent functions.

The problem is, all database access is external state. This means that any method that hits the database is no longer referentially transparent.

Lets say I have a user object in a database and some functions that deal with authentication.

Referentially opaque functions might look like:

handle_web_request(http_info) -> is_authorized_user(http_info.userid), ... %referentially opaque is_authorized_user(userid) -> User = get_user_from_db(userid), User.is_authorized. %referentially opaque lots_of_other_functions(that_are_similar) -> db_access(), foo. 

Referentially transparency requires that I minimize the amount of referentially opaque code, so the caller must get the object from the database and pass that in as an argument to a function:

handle_web_request(http_info) -> User = get_user(http_info.userid), is_authorized_user(User), ... %referentially opaque get_user(userid) -> get_user_from_db(userid). %referentially transparent is_authorized(userobj) -> userobj.is_authorized. %referentially transparent lots_of_other_functions(that_are_similar) -> foo. 

The code above is obviously not production code - it is made up purely for illustrative purposes.

I don't want to get sucked into dogma. Do the benefits of referentially transparent code (like provable unit testing) justify the less friendly interface? Just how far should I go in the pursuit of referentially transparancy?

1
  • You can just get rid of the whole database, having only a loop(MyWholeState) -> loop(receive X -> X end, MyWholeState) and loop(RecvHttp, MyWholeState) -> ... , loop(MyWholeNewState) function, making everything referential transparent. Commented Nov 21, 2009 at 17:04

2 Answers 2

9

Why not take referential transparency all the way?

Consider the definition of get_user_from_db. How does it know how to talk to the database? Obviously it assumes some (global) database context. You could change this function so that it returns a function that takes the database context as its argument. What you have is...

get_user_from_db :: userid -> User 

This is a lie. You can't go from a userid to a user. You need something else: a database.

get_user_from_db :: userid -> Database -> User 

Now just curry that with the userid, and given a Database at some later time, the function will give you a User. Of course, in the real world, Database will be a handle or a database connection object or whatever. For testing, give it a mock database.

Sign up to request clarification or add additional context in comments.

3 Comments

Now that I've read your answer, it seems totally obvious. But I would have never thought of it - thanks! However, it seems to me that it would make more sense (in this circumstance) to write function that returns a function that has a database context: get_user_from_db :: Database -> userid -> User I suppose this version potentially breaks referential transparency since the underlying database context may change...
You can get one from the other. a -> b -> c is equivalent to b -> a -> c. All you need is a higher-order function: flip f a b = f b a. The order of arguments a design choice. In this particular case it makes sense for composability to put Database as the last argument, since that allows you to do Kleisli composition.
Having Database as the first argument, a function can only be composed with functions that return databases. I suspect there aren't many of those.
3

You already mentioned unit-testing, keep thinking in those terms. Everything you find value in testing should be refentially transparent so you can test it.

If you don't have any complex logic that could go wrong, and a single functional/integration test would see that it is correct, then why bother going the extra distance?

Think YAGNI. But where unit-testability is a real need.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.