24

I found myself struggling to see what is this principle about and why is it so important for language design.

Basically, it states, that for every expression expr in language should be exactly the same as this construct:

(function () { return expr; })()

Also, I have heard that Ruby obeys this principle, while Python doesn't. I don't understand why this is true, or if it is true at all.

Andrew
  • 343
  • 2
  • 5

5 Answers5

20

I have never heard before of "Tennent's Correspondence Principle" and even less of it being important in language design. Googling the expressions seems all to lead to one Neal Gafter blog of 2006 that lays down what he thinks it is and how he thinks it should apply to closures as well. And most all others in forums seem to refer to Gafter's entry.

Here is however a mentioning of said "T.C.P." by Douglas Crockford (a name i know and trust): http://java.sys-con.com/node/793338/ . In part

There are some things that can't be enclosed that way, such as return statements and break statements, which the advocates of Tennent's Correspondence Principle (or TCP) claim is a symptom of a bad smell. Yow! Language design is already difficult enough without having to cope with olfactory hallucinations. So to better understand the problem, I bought a copy of Tennent's 1981 book, Principles of Programming Languages.

It turns out that the Correspondence Principle is descriptive, not prescriptive. He uses it to analyze the (by now forgotten) Pascal programming language, showing a correspondence between variable definitions and procedure parameters. Tennent does not identify the lack of correspondence of return statements as a problem.

So seems therefore the name "Tennent's Correspondence Principle" is mis-used, and whatever Neal talks about possibly should be called "Gafter's Imagined and Possibly Generalized T.C.P."... or some such. In any event not enough to hide behind an out-of-print book name-curtain

Nas Banov
  • 316
  • 2
  • 6
9

I see this as part of a general rule that a well designed language does what a programmer would naturally expect. If I have a block of code that I want to refactor into a closure, and I wrap that block with the appropiate syntax without really thinking about the individual lines of code, then I expect that block to do the same thing in the closure as it did inline. If some statements use the keyword "this" (perhaps implicitly) and the language makes "this" used inside the closure refer to an anonymous class used to represent it rather than the class that defines the method that defines the closure, then the meaning of those statements has changed, my block of code no longer does what I think it does, and I have to track down a bug and figure out how to change my code to work in the closure. A language that prevents me from having such problems is helpful.

The problem could also be mitigated with an IDE with smart refactoring tools, that could extract closures, detect potential problems, and even automatically adjust the extracted code to solve the problems.

JGWeissman
  • 1,061
  • 8
  • 12
3

Claus Reinke: regarding Tennent's "Language Design based on Semantic Principles"
Gives interesting interpretation of the principles:
"Correspondence is the principle that lets us say that

let(this=obj, x=5) { .. }  

and

((function(x) { .. }).call(obj,5))  

should be equivalent, and that anything we can do in formal parameter lists, we should also be able to do in declarations, and vice versa." [see also, Reinke, below.]

R. D. Tennent: Language design methods based on semantic principles
"Two language design methods based on principles derived from the denotational approach to programming language semantics are described and illustrated by an application to the language Pascal. The principles are, firstly, the correspondence between parametric and declarative mechanisms, and secondly, a principle of abstraction for programming languages adapted from set theory. Several useful extensions and generalizations of Pascal emerge by applying these principles, including a solution to the array parameter problem, and a modularization facility."

Claus Reinke: "On functional programming, language design, and persistence" on Haskell

Glorfindel
  • 3,137
  • 6
  • 25
  • 33
Kris
  • 304
  • 1
  • 7
  • Claus Reinke: "On functional programming, language design, and persistence" on Haskell at http://community.haskell.org/~claus/publications/fpldp.html – Kris Nov 18 '11 at 09:10
2

To answer the question why Tennent's CP is so important for language design, I'd like to quote Neal Gafter:

Tennent's principles are very powerful because violations of them tend to show up in the language as flaws, irregularities, unnecessary restrictions, unexpected interactions or complications, and so on.

Any violation of TCP is likely to hurt some programmer in the future when he expects closures to work like non-closure code but finds that, in violation of TCP, they don't.

thiton
  • 5,348
  • 1
  • 27
  • 26
1

RE Python not following this principle. Generally, it does follow the principle. Basic example:

>>> x = ['foo']
>>> x
['foo']
>>> x = (lambda: ['foo'])()
>>> x
['foo']

However, Python defines expressions and statements separately. Since if branches, while loops, destructive assignment and other statements cannot be used in lambda expressions at all, the letter of the Tennent principle does not apply to them. Even so, restricting oneself to using only Python expressions still produces a Turing complete system. So I do not see this as a violation of the principle; or rather, if it does violate the principle, then no language that defines statements and expressions separately can possibly conform to the principle.

Also, if the body of the lambda expression were capturing a stack trace, or doing other introspection in the VM, that could cause differences. But in my opinion this should not count as a violation. If expr and (lambda: expr)() necessarily compile to the same bytecode, then the principle really concerns compilers not semantics; but if they can compile to different bytecode, we should not expect VM state to be identical in each case.

A surprise can be encountered using the comprehension syntax, though I believe this is not a violation of the Tennent principle either. Example:

>>> [x for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [lambda: x for x in xrange(10)]]  # surprise!
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> # application of Tennent principle to first expression
... [(lambda: x)() for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [(lambda x: lambda: x)(x) for x in xrange(10)]]  # force-rebind x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> map(lambda f:f(), map(lambda x: lambda: x, xrange(10)))  # no issue with this form
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The surprise is a result of how list comprehensions are defined. The above 'surprise' comprehension is equivalent to this code:

>>> result = []
>>> for x in xrange(10):
...   # the same, mutable, variable x is used each time
...   result.append(lambda: x)
... 
>>> r2 = []
>>> for f in result:
...   r2.append(f())
... 
>>> r2
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Seen this way, the 'surprise' comprehension above is less surprising, and not a violation of the Tennent principle.

wberry
  • 481
  • 3
  • 8