Tuesday, September 30, 2008

Hidden Functions

Let's talk about hidden functions. What do I mean by that? When I refer to a hidden function, I mean one that exists in some non-public context, and is not directly accessible from the outside. There are several ways one can realize this goal, and we'll talk our usual stroll through some appropriate languages and how they approach his problem.

Scheme



Let's start with Scheme. Assuming the follow content in a file called hidden.scm:

(define (obvious)
(define (hidden) `hidden)
(hidden))


We can then try it out using guile -l hidden.scm:


guile> (hidden)

Backtrace:
In standard input:
1: 0* (hidden)

standard input:1:1: In expression (hidden):
standard input:1:1: Unbound variable: hidden
ABORT: (unbound-variable)


We see that (hidden) is not directly accessible from the outside, however...


guile> (obvious)
hidden


It is still defined and reachable through the intermediary function (obvious). This is great for all the expected black box reasons:


  1. Users/developers can be presented with a consistent interface

  2. Purpose can be separated from implementation details, which means that

  3. Testing is easier to do, which means that

  4. Refactoring is easier to do, whether literally just an improvement to internal clarity and readability, or wherein the inputs and outputs remain the same expressions, but other improvements occur, such as faster speed of execution, reduced memory footprint, or both



Perl



I've written about Perl a bit more than I expected to lately. Let's continue with that, and see an example in Perl.


#!/usr/bin/env perl
# hidden.pl

use constant HIDDEN => 'hidden';

sub obvious {
sub hidden { HIDDEN }
hidden();
}

sub obvious2 {
my $hidden = sub { HIDDEN };
$hidden->();
}

print obvious() . "\n";
print obvious2() . "\n";
print hidden() . " is really not that hidden\n";
print $hidden->() . "\n";


Executing this program at the command line with perl hidden.pl produces the following output:


hidden
hidden
hidden is really not that hidden
Undefined subroutine &main:: called at hidden.pl line 19.


I like constants for values that don't change and are reused - call me crazy. The examples obvious() and obvious2() are very similar. The only difference is that the function which we wish to hide is declared either as a traditional subroutine or as a subroutine reference. Why the distinction?

The reason is revealed when we make a direct call to hidden(). Perl does not scope the declaration of hidden() within obvious() as Scheme does. hidden() is accessible from outside obvious() where it was declared. However, attempting to call $hidden->() at the top level fails, because Perl does scope $hidden inside obvious2() where it was declared.

I'm not a real Perl guru. I get by in the language, but I'm sure someone who frequents Perl Monks or the like could explore this idea further.

Ruby



Despite Ruby's comprehensive support for programming in the functional paradigm, it is commonly thought of as an object oriented language, not without cause. Many people are most familiar with the notion of hiding executable code through the process of private or protected access control common in OO languages.

I won't give an example of traditional OO access control in Ruby here. Rather, I'll explore ideas more akin to the preceding Scheme and Perl examples that use nested function declarations. Some of this code is intentionally broken and/or strange-looking.


class Owner

HIDDEN_PROC = lambda { %q[hidden value from Proc] }

def obvious
def hidden; %q[hidden value]; end
hidden.gsub(%r[hidden], %q[obvious])
end
def obvious2; hidden + %q[ from obvious2]; end
def obvious3; HIDDEN_PROC.call; end

end

if (__FILE__ == $0)
o = Owner.new
puts o.obvious
puts o.obvious2
puts o.obvious3
begin
puts o.obvious.hidden
rescue
puts %q[Could not access o.obvious.hidden]
end
begin
puts o::HIDDEN_PROC.call
rescue
puts %q[Could not access o::HIDDEN_PROC.call]
end
puts o.hidden + %q[ - not really all that hidden]
end


Let's take it for a spin with ruby hidden.rb. (Note that the boolean expression (__FILE__ == $0) is only truthy when the file is executed directly like this, and not when the file is required in the manner of a library file.)


obvious value
hidden value from obvious2
hidden value from Proc
Could not access o.obvious.hidden
Could not access o::HIDDEN_PROC.call
hidden value - not really all that hidden


hidden() is callable from within obvious(), and we can even do a regular expression substitution on its value. Fine and dandy. Its hidden status is called into question by it being accessible from within obvious2(), even though it was not declared there. Let's ponder why that is for a moment.

If a function in Ruby were genuinely hideable, as in Scheme, it would not be so available. However, one must keep in mind that there are technically no functions in Ruby at all: they're methods, and that distinction makes all the difference. A method is a function that is attached to an object (or class), and what would it imply if a method were not accessible by its owning object? It wouldn't be much of a method, and that's where Ruby's OO nature wins out.

In order to restrict access to some executable code within an object, we can either comply with OO practice and use traditional access control, or do something akin to obvious3(), in which we define a callable HIDDEN_PROC that is not publicly accessible, but is callable from within obvious3() (for example). If we wanted even more separation, we could define HIDDEN_PROC as a lexical variable within the obvious3() method, rather than as a class Constant. Such an approach would be very similar to the Perl example's definition of $hidden.

Erlang



On to Erlang, which has some interesting features regarding exporting of functions. Here's our file hidden.erl.


-module(hidden).
-author("Kevin C. Baird").
-purpose("Demonstrate hidden functions in Erlang").
-export([obvious/0]).

obvious() -> hidden().
hidden() -> 'value from hidden'.


Here's our erl session that uses it:


$ erl
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.5.5 (abort with ^G)
1> c(hidden).
{ok,hidden}
2> hidden:obvious().
'value from hidden'
3> hidden:hidden().

=ERROR REPORT==== 30-Sep-2008::10:05:09 ===
Error in process <0.31.0> with exit value: {undef,[{hidden,hidden,[]},{erl_eval,do_apply,5},{shell,exprs,6},{shell,eval_loop,3}]}

** exited: {undef,[{hidden,hidden,[]},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_loop,3}]} **


Because only obvious is exported, it is the only function publicly available. We could have used as complex a basis as we wanted to determine its eventual value, all of which could be safely hidden within our hidden module. That's what encapsulation's all about.

Haskell



Another purely functional language I've discussed is Haskell. In fact, its strongest advocates would probably deny Erlang the label purely functional, as it does not segregate side effects as fully as Haskell does.

In any case, we can have a quite small Hidden.hs file:


-- Hidden.hs
-- Kevin C. Baird
-- Demonstrate hidden functions in Haskell

obvious = hidden
where hidden = "value from hidden"


That executes as you'd expect by this point, as seen in hugs:


$ hugs Hidden.hs
__ __ __ __ ____ ___ _________________________________________
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2005
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Bugs: http://hackage.haskell.org/trac/hugs
|| || Version: September 2006 _________________________________________

Haskell 98 mode: Restart with command line option -98 to enable extensions

Type :? for help
Main> obvious
"value from hidden"
Main> hidden
ERROR - Undefined variable "hidden"

1 comment:

crallspace said...

Hey Kevin... dan here from The Sound Experiment. You sent me a disc of CRUDE FRIENDLY years back which I used on the show. Listening to it now. Great stuff!

Take care.