Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How far should I take referential transparency?

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?

like image 593
Abtin Forouzandeh Avatar asked Feb 04 '23 08:02

Abtin Forouzandeh


1 Answers

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.

like image 183
Apocalisp Avatar answered Feb 09 '23 01:02

Apocalisp