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:
- Users/developers can be presented with a consistent interface
- Purpose can be separated from implementation details, which means that
- Testing is easier to do, which means that
- 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"