Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

MOPT-04 - identification, part 2

by mstone (Deacon)
on Jan 07, 2003 at 01:39 UTC ( [id://224813]=perlmeditation: print w/replies, xml ) Need Help??

Meditations on Programming Theory
meditation #4: Identification, part 2: functions

In MOPT-03, I said that the theoretical difference between functions and variables was arbitrary, but useful. This time I'll explain why, and show how functions and variables work together.

Functions:

functions and variables:

A function has all the same features a variable does: A function is an entity with an abstraction barrier, and that barrier defines a scope. Every function can be bound to an identifier, and every function produces a value. The set of all values a function can produce defines a type.

Function notation actually makes those pieces easier to see than variable notation. For a simple function declaration:

    sub function { return ("value"); }
  • The keyword 'sub' defines an entity.
  • The braces '{' and '}' mark that entity's abstraction barrier.
  • The string 'function' is the identifier bound to that entity.
  • The keyword 'return' marks the entity's value.
  • And the string 'value' is the value itself.

Since the function above always returns the same value, it's more or less equivalent to a variable.. at least to the kind of variable we saw back in MOPT-03. In theory, you should be able to replace any variable with an appropriately trivial function, but Perl doesn't quite support that idea.

Theoretically, the code:

    sub func { return "original value.\n" }
    sub print_func { print func(); }
        
    sub redefine_func {
        sub func {
            return ("local value.\n");
        }
        print_func();
    }
    
    print_func();
    redefine_func();

could produce the output:

    original value.
    local value.

which would make func() behave like a dynamically-scoped variable. It doesn't, though. The actual output is:

    local value.
    local value.

which shows us that the second definition completely replaces the first.

Perl's function bindings are statically scoped (1), not dynamically scoped, so we can't bind the name to a new entity in a different evaluation context. If we want to play with dynamically scoped entities in Perl, we have to stick to local variables. Theoretically, though, there's nothing to stop us from creating a language that binds names to functions dynamically. And in such a language, there would be no difference between a trivial function and a variable.

(1) - BIG HONKIN' CORRECTION: adrianh correctly showed, below, that you can dynamically redefine functions by assigning an anonymous subroutine to a local typeglob:

sub redefine_func { local (*func) = sub {return ("local value.\n")}; }

makes a function behave exactly the same way as a local variable. Kudos and thanks adrianh!

Officially, variables are degenerate functions. The term 'degenerate' indicates the limiting case of an entity, which is equivalent to some simpler entity. We usually find degenerate entities by setting some attribute to zero. A point is a degenerate circle, for instance: a circle with radius zero.

In the case of functions and variables, the thing we set to zero is the function's parameter list.

functions, parameters, and formal structures:

A parameter is a variable whose value affects a function's value, but not its formal structure.

A formal structure is the set of operations that make a function do whatever it does. Those operations will always boil down to a pattern of substitutions, and we can always build a formal system (see MOPT-02 for more about formal systems) that uses those substitutions as rules.

A formal structure isn't a complete formal system, though. It's just a computation that happens within a formal system. Officially, a formal structure describes a family of 'generally similar' computations that can take place in a given formal system, and a function's parameters are axioms on which those computations are performed.

Which is great, as long as we know what 'generally similar' means. To explain that, we need more vocabulary.

The rules that make up a formal system fall into one of two categories.. equivalence rules and transformation rules:

  • In an equivalence rule, the replacement string represents the same meaning as the original string.
  • In a transformation rule, the replacement string represents a different meaning than the original string.

The concepts of equivalence and meaning are equipotent: they have equivalent power. We can define either one in terms of the other, so if we have one, we automatically have both:

  • If we know that two strings represent the same meaning, we can write an equivalence rule that replaces one string with the other (sometimes.. we'll get to the details in a minute).

  • If we know that a rule is an equivalence rule, we know that the strings on either side of that rule are alternative representations of the same thing.. even if we don't have any idea what that thing happens to be.

The process of assigning meanings to symbols is called interpretation. Interpretation is central to programming, and indeed to most of mathematics, and is a subtle, often infuriatingly complex subject. The problem boils down to the fact that different representations can embody different sets of assumptions, and a substitution that makes perfect sense for one representation might be meaningless for another:

    print (
        "2 + 2 ",
        (2+2 == 4) ? "is" : "is not",
        " equal to 4.\n"
    );
    print "- but -\n";
    print (
        "'two plus two' ",
        ('two plus two' eq 'four') ? "is" : "is not",
        " equal to 'four'.\n"
    );

It can be very difficult to build a set of equivalence rules for different representations of the same meaning, and still maintain a coherent sense of overall meaning. A set of narrowly defined equivalences, each of which makes sense in its own way, is almost guaranteed to produce contradictions when taken as a whole.

  • An equivalence that's well enough behaved to use as a substitution rule is called a formal equivalence.

  • An equivalence that we acknowledge as humans, but don't represent formally (i.e.: with a rule) is called an informal equivalence or an interpretation.

We use both ideas when programming, but only certain substitutions count as equivalence rules. The others are just transformations where both sides of the rule happen to have similar interpretations.

Formal equivalence is transitive. The transitive property says that if 'a' equals 'b' and 'b' equals 'c', then 'a' equals 'c'. In other words, we can condense any sequence of equivalence rules into a single rule. In programming, we call that process reduction and say that two strings are reducible if:

  1. we can derive one string from the other
  2. we can derive both strings from each other
  3. or we can derive a third string from both original strings

The set of all strings we can derive from a single axiom by repeatedly applying a single rule is called a transitive closure, or just a closure. An equivalence rule breaks a language into a set of mutually-exclusive closures called partitions. Any two strings from the same partition are formally equivalent, and no string in a partition is reducible to any string in any other partition.

Those partitions mark the difference between equivalence rules and transformation rules. Only an equivalence rule can partition a language. The closure of a transformation rule contains the entire language.

We use the term 'reduction' because formal equivalence is slightly different from logical equivalence. Logical equivalence is symmetric: if 'a' equals 'b', then 'b' equals 'a'. Substitution rules aren't symmetric, though. They only work in one direction. To make a formal equivalence symmetric, we'd need two rules, each going the opposite direction. Only the second reduction principle, above, is symmetric. The first and the third are asymmetric.

That asymmetry is what makes the whole subject of interpretation so tricky. A pair of strings can be formally equivalent, even if the derivation only works one way. Worse yet, the partitions for two different languages might not line up with each other, even though the interpretations are logically equivalent. We can say that the strings 'two' and 'TWO' are both logically equivalent to the number 2, but those strings might not be formally equivalent to each other.

To get back to functions, with those new terms in our arsenal, we can say that:

  • A function's formal structure defines a sequence of transformation rules, along with any equivalence reductions necessary to turn the result of one transformation into a target for the next.

In practice, that means code tends to gather in chunks where we change something, then rearrange the result. That change, then rearrange sequence is a low-level design practice called an idiom.

Idioms represent a layer of logical structure between syntax and functions. We use syntax to build idioms, and we use idioms to build functions. There are no hard-and-fast rules about idiomatic programming, just a handful of low-level structures that pop up often enough to be familiar. Idioms give us a framework from which to hang code, and code tends to be easier to read, write, and use if we use that framework consistently.

  • A function's parameter list is the set of original values that get transformed and rearranged until they become the function's value.

A variable, being a degenerate function, takes no parameters. Any axioms necessary are hardwired into the function, so the formal structure produces the same value every time.

Parameters share the same strange, half-way existence as values, since both parameters and values can cross an abstraction barrier. Parameters are visible in two different scopes at the same time, and making that happen takes special machinery.

functions and bound variables:

Within the scope of a function, a parameter is just another free variable. It has a name, but that name isn't bound to any entity defined in the same scope. A parameter doesn't receive its binding from the global context, or from the function's evaluation context, either. It gets bound to an entity defined in the statement that calls the function.

  • A statement that calls a function and supplies it with parameters is officially known as an invocation context.

  • A free variable that represents a parameter is officially known as a bound variable, or formal parameter.

  • The entity in the evaluation context that gets bound to the formal parameter is called the actual parameter.

  • The system that handles those bindings is called the parameter passing mechanism.

Parameter passing mechanisms come in three basic flavors: positional, named and defaulted:

  • Positional parameters are the most common. The formal parameters appear in a specific order when the function is defined, and actual parameters are bound to the appropriate names based on their order in the invocation context.

  • Named parameters are less common. The actual parameters can occur in any order, because you list them with their names in the invocation context.

  • Defaulted parameters are tricky. The default value is defined as part of the function's definition, and the compiler creates a behind-the-scenes entity for that value at compile time. At runtime, the formal parameter starts off being bound to that entity, but gets re-bound to the actual parameter, if an appropriate entity exists in the invocation context.

Perl happens to use an interesting variation on positional parameters. Every function takes exactly one parameter: a list. The contents of that list are defined in the invocation context.

At first glance, that seems like a cheap way to avoid building a 'real' parameter passing mechanism, like C or Java have, but it's actually quite elegant from a theoretical standpoint. A Perl function can also return a list, so 'the list' forms a standard interface between any two functions. When we add the rest of Perl's list-handling tools, that interface makes Perl very good at handling signal processing logic, where each function acts like a filter on an arbitrarily long stream of input:

    sub sieve {
        my ($prime, @list) = @_;
        
        if (@list) {
            return ($prime, sieve (grep { $_ % $prime } @list));
        } else {
            return ($prime);
        }
    }
    
    print join (' ', sieve (2..50)), "\n";

The code above implements the Sieve of Eratosthenes with signal processing logic. The function sieve() assumes that the first element of the list is a prime, and it filters any multiple of that prime out of the remaining list. The first element of the new list will always be prime (because any composite of smaller primes has already been filtered out), and the function recurs with that list. The result is a list of all the prime numbers in the original list:

    2 3 5 7 11 13 17 19 23 29 31 37 41 43 47

Signal processing is another idiom, and functional programmers use it heavily. Signal processing programs tend to be easy (okay.. easier) to analyze mathematically, so you can prove that the program will behave properly if you're willing to do the work.

Perl also makes it easy to simulate named parameter passing:

    sub use_pseudo_named_parameters {
        my (%args) = @_;
        ...
    }
    
    use_pseudo_named_parameters (
        'arg1'  =>  'val1',
        'arg2'  =>  'val2',
        'arg3'  =>  'val3',
    );

And defaulted parameter passing:

    sub use_pseudo_defaulted_parameters {
        my (%args) = @_;
        my %params = (
            'param1'    =>  'default 1',
            'param2'    =>  'default 2',
            'param3'    =>  'default 3',
        );
        @params{ keys %params } = @args{ keys %params };
        undef (%args);
        ...
    }
    
    use_pseudo_defaulted_parameters (
            'param1'    =>  'invocation value 1',
            'param2'    =>  'invocation value 2',
    );

so instead of sticking us with a single parameter passing mechanism, Perl makes it reasonably simple to simulate any mechanism we want.

functions and identification:

Now in purely theoretical terms, there's no reason to worry about name bindings at all. Instead of thinking of a function as a machine that converts parameters into values, like a machine in a factory, we can think of a function as a family of entities that all have the same formal structure. Each member of that family would have a different set of values hardwired in as parameters, making them variables according to the terms I mentioned earlier.

By that reasoning, the string 'func(1)' would be the name of a specific variable. the '(1)' part would just be a naming convention, not an invocation context that establishes a binding. I used a similar naming convention for hash keys in the code samples, above.

Yes, the 'machine in a factory' version is generally easier to implement in a real computer, but the 'family of variables' version is also possible, and is occasionally useful. I'll explain how in next week's meditation, which will cover lvalues.

For now, the point is that there's no theoretical difference between passing arguments to a function and selecting a specific variable from a set. As a matter of fact, the 'family of variables' model is actually closer to the mathematical definition of a function than the 'machine in a factory' version.

And that brings us all the way back to the concept of identification. The ability to select a specific item from a set gives the power to do anything a function can do. When we get right down to it, functions are just a convenient fiction that make it easier to describe the kinds of things we can do with identification.

Replies are listed 'Best First'.
Re: MOPT-04 - identification, part 2
by adrianh (Chancellor) on Jan 07, 2003 at 17:34 UTC
    Perl's function bindings are statically scoped, not dynamically scoped, so we can't bind the name to a new entity in a different evaluation context.

    You can, of course, do:

    sub func { return "original value.\n" } sub print_func { print func(), " "; } sub redefine_func { local *func = sub { return ("local value.\n") }; print_func(); }

    Which is still binding an identifier to an entity by your definitions (I think :-)

      well darned if you aren't right..

      The syntax you used does exactly the same thing as a standard function definition, except it does it locally. By all rights, defining a function with a name should be syntactic sugar for assigning an anonymous subroutine to a typeglob. Apparently it isn't, and I missed it.

      Bravo.. I stand corrected. And so does the article.

Re: MOPT-04 - identification, part 2
by theorbtwo (Prior) on Jan 07, 2003 at 02:12 UTC

    One quick question: "The set of all strings we can derive from a single axiom by repeatedly applying a single rule is called a transitive closure, or just a closure." Where does the name "closure" come from? How is this related to a variable-binding-capturing "closure", as in &closure in

    { my $foo; sub closure { $foo++ } }
    (I know, I'm getting into lvalues here.)


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

      • Where does the name "closure" come from?

      I just realized this morning that I'd used the word 'closure' without explaining the two different meanings of that word.. my bad. Set closures and 'value capturing' closures are entirely different creatures, which trace their origins back to different mathematical definitions for the word 'closed'.

      Regarding set closures, we say that a set S is closed for a certain operation '.' (where '.' is the traditional symbol for some unspecified operation) if 'a.b' is a member of S for every a and b in S. A set is open if a and b are in S, but 'a.b' isn't.

      The set closure of S for 'a' under '.' is the smallest closed subset of S that contains a for operation '.': {'a', 'a.a', 'a.a.a', ...}

      • How is this related to a variable-binding-capturing "closure"

      Different critter entirely.. while the set theory people were coming up with their definition of the word 'closed', the predicate calculus people were using it to mean something else.

      Predicate calculus (aka: formal logic) revolves around statements called sentences. Each sentence indicates some relationship between basic entities, known as predicates. Without getting too technical, a predicate is a statement that has a truth value, and the sentence says something about the relationship between the truth values of its predicates.

      Sentences can also contain variables, which work pretty much like function parameters. A sentence where every variable is bound to some predicate is called a closed sentence. A sentence with an undefined variable is called an open sentence.

      A 'value capturing' closure binds an entity to one of a function's free variables, so in predicate calculus terms, it closes one variable in the function.. hence the term closure.

Re: MOPT-04 - identification, part 2
by Courage (Parson) on Jan 07, 2003 at 12:53 UTC
    I have a correction to your first part of a message
    Perl's function bindings are statically scoped, not dynamically scoped, so we can't bind the name to a new entity in a different evaluation context. If we want to play with dynamically scoped entities in Perl, we have to stick to local variables.

    Actually you can redefine subroutine by using eval "sub func {'...'}" and better yet is just to use anonymous subroutines and use ordinary variables to switch between different ones:

    my $func = sub { return "value"; }; $func->(); $func = sub { return "another value"; }; $func->();

    Courage, the Cowardly Dog

      • Actually you can redefine subroutine by using eval "sub func {'...'}"

      That would create a new function at runtime, but it wouldn't follow the rules of dynamic scoping (see MOPT-03 for details).

      As you've used it, the eval() just binds a new function to the name 'func'. To be dynamic, the new version of func() would have to be restricted to the evaluation context of the function that defined it:

      $VAR = "old VAR"; sub func {return ("old func()")} sub show {printf "VAR='%s', func()='%s'\n", $VAR, func()} sub redefine { eval 'sub func {return ("new func()")}'; local ($VAR) = "new VAR"; show(); } show(); redefine(); show();

      returns:

      VAR='old VAR', func()='old func()' VAR='new VAR', func()='new func()' VAR='old VAR', func()='new func()'

      which shows the difference between redefining a globally-scoped function and re-binding a dynamically-scoped variable.

      • and better yet is just to use anonymous subroutines and use ordinary variables to switch between different ones:

      Yes, that would work:

      $VAR = "old VAR"; $FUNC = sub {return ("old func()")}; sub show {printf "VAR='%s', func()='%s'\n", $VAR, &$FUNC()} sub redefine { local ($FUNC) = sub {return ("new func()")}; local ($VAR) = "new VAR"; show(); } show(); redefine(); show();

      returns:

      VAR='old VAR', func()='old func()' VAR='new VAR', func()='new func()' VAR='old VAR', func()='old func()'

      which does the right thing. But to get the effect of a dynamically-scoped function, you have to use a dynamically scoped (aka: local) variable, and I did say that in the original article.

      HOWEVER.. check out the next reply to see how I was, in fact, wrong.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://224813]
Approved by adrianh
Front-paged by djantzen
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (6)
As of 2024-03-28 19:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found