Programming Constructs: Explicit vs. Implicit
As a person who has studied many languages (both organic and mechanical), I have found there tends to be a critical separation between semantics that can ruin your day if you're not prepared for it.
Programming constructs fall into one of two giant bins: explicit or implicit. If you're new to programming, or have only used a handful of languages, this might sound like a new concept. And, depending on the languages you used earlier in life, you might feel that one is superior to the other. This article will focus on the differences of each methodology and discuss the benefits and consequences of using either in a commercial application.
First of all, I'll need to illustrate a few examples so everyone understands what I mean. Chances are, you might already understand implicit type conversions. In many languages (especially in runtime interpreted environments), the developers decided to relax type enforcement in favor of automating some of the mundane tasks commonly encountered in high level languages. For example, if you want to cast one numeric type into another, you might write something like this:
unsigned char my_number = 42; int my_new_number = (int)my_number + 256;
This tells the (C) compiler to properly expand the bits stored in
my_number into the memory space referenced by
my_new_number. (Some call this a "widening" type cast which
has the result of adding a few leading zeros to the original number.)
This is explicit type casting.
Now, if you've seen very much code that has to interact with multiple interfaces (users, communication layers, other processes, library code, etc), you've no doubt seen how frequent application programmers have to use type casting when moving information between these interfaces. This is why "higher" level languages tend to prefer implicit type casting. This not only cleans up the minor annoyance of using the casting syntax, but provides a seemless method for creating complex data types on the fly. Maybe instead of just adding a small number to a larger one, we add a string containing characters that might represent a number to another number:
$my_number = '42'; $my_new_number = $my_number + 256;
Most script programmers don't see anything fishy going on here. But, there
is. Script programmers will naturally just assume the integer
298 is stored in $my_new_number. And, in places
like Perl, they'd be right. (Assuming this worked in C,
$my_new_number would be set to
((0x34 << 8) + 0x32) + 256 or 13618!)
In interpreted languages, all that "fun" stuff is done for you and is based on a more complex set of rules about what types (given their value) can be cast into what other types. The set of rules—in this case—are largely logical for most programmers, so we see it as a convenience.
Implicit type casting certainly has its advantages, and because the abstraction is usually very intuitive, we like having it in those environments where we are "gluing" many interfaces together. The frictional component of the problem, ironically, is a result of the convenience of implicit type casting.
At some point in language history, it seems like a good number of language/interpreter developers decided to expand on this idea. They wanted to further "help" developers by providing short cuts and abbreviated syntax. The classic (and possibly most evil) manifestation of this trend is the implicit function or implicit routine.
The idea is pretty simple if you think global variables are valid storage locations. First, you set up some information that the routine will use, then you call the routine, then you somehow reference the results this routine had on your information. This pseudocode might demonstrate a possible scenario:
argument_1 = 42; argument_2 = 2; add_numbers; display_number;
Let's pretend that this is the only code in the script, and it displays
44 on a screen or in a file. This example of implicit
methodology exposes several attempts at "helping" the programmer.
First, we see some assumptions being made about how we need to name
our (global) variables.
Second, the destination or place where the sum is held is not immediately
recognizable by the casual observer.
Third, assuming the display_number routine understood where
add_numbers stored its value, we don't know where this
number is going (screen, printer, file, web page) and how it will be
formatted when it reaches that destination.
For the person who wrote those routines, it probably looks like very simple code. For a person learning the language, this might be one of a hundred different ways routines use arguments in this environment, and keeping track of which way works for which situation is an unnecessary set of knowledge if all routines act the same way. For a programmer who comes along and needs to adjust some detail of operation (e.g. how the number looks at its destination), the "helpful" assumptions of the environment might not have the flexibility to support the desired modification.
Let's look at the same basic pseudocode where everything is explicit:
argument_1 = 42; argument_2 = 2; sum = add_numbers(argument_1, argument_2); display_number(STDOUT, 10.0, sum);
Understandably, this looks more complex than the largely implicit version.
I've also made a rather hasty assumption that the standard file handle
STDOUT exists. (My example implies someone set this
up earlier in the program. GASP!) Additionally, the person who wrote the
display_number routine was saddled with documenting the
parameters that needed to make the routine flexible.
Of course, there's still a level where the inner details are hidden from us (even in assembly, you're abstracted from the details behind an interrupt or a poll). Strong, useful abstraction shouldn't be confused with implicit operation. The difference is abstraction operates behind a hidden layer where the inner details are unimportant and never need to work any differently. Implicit operation, however, infers the user has some knowledge of the internal details (the opposite of good abstraction), and that all users seek to use the routine for the same purpose every time. This leads to the maintainers of implicit systems referring to their routines as "magic." They do things most users expect, but things that may be completely unnecessary for other, reasonable uses.
Because of this, implicit systems also suffer the weakness of less adaptability to unexpected solution environments. A good example is languages whose intended environment is an interactive console (such as TCL or Forth). Both of these interpreted languages utilize heavy inference because they intend to interact primarily with people and not network stacks, other processes, or many other idiomatic interfaces. When used to generate a web page, or build a packet server, these environments are fully capable, but nearly all the I/O has to be handled as an exceptional case with often confusing/contradicting syntax.
Most of the BASIC derivatives (that still exist on Microsoft platforms) continue the original implicit patterns that rely on poor scope enforcement and weak abstraction.
Explicit environments tend to be the more established, mainstream tools. C, C++, and Java are some of the more common explicit languages. Abstraction in these languages is easy (or required, in the case of Java), and relying on side-effects of various operations doesn't exist in any of the typical libraries or core language.
One thing that has always struck me as odd is how much implicit forms impact the way Perl works. There are many things in Perl that can't be done without implicit forms. We basically accept that Perl is excellent at processing vast amounts of text at a cost of esoteric, write-only code. One of my heros, Larry Wall, liked to express that there's always more than one way to do it with Perl. Unfortunately, with that philosophy, he created the programming equivalent of a Rubiks Cube. On the surface, Perl is simple and concrete, but the sheer number of possibile solutions to the same problem make reading Perl a lesson in patience (even if it's your own code). The biggest reason I have difficulty reading Perl is the use of implicit operations (a close second is the attempt at mixing natural-language with structured, punctuated syntax).
Claiming Perl as its origin language, PHP (while not technically functional), gets rid of most of the implicit operations Perl relied on. Implicit type conversions remain, but functional and OOP structure make abstraction convenient much more C-like than Perl ever was.
{Work in progress.}