What is Expressiveness in a Computer Language

Duane Rettig duane at franz.com
Tue Jun 27 17:53:44 CEST 2006


Ketil Malde <ketil+news at ii.uib.no> writes:

> "Marshall" <marshall.spight at gmail.com> writes:
>
>> There are also what I call "packaging" issues, such as
>> being able to run partly-wrong programs on purpose so
>> that one would have the opportunity to do runtime analysis
>> without having to, say, implement parts of some interface
>> that one isn't interested in testing yet. These could also
>> be solved in a statically typed language. (Although
>> historically it hasn't been done that way.)
>
> I keep hearing this, but I guess I fail to understand it.  How
> "partly-wrong" do you require the program to be?

This conclusion is false.  To be "wrong", whether partly or fully,
a program needs to specifically not conform to the requirements that
the programmer gives it.  You are assuming that only a completed
program can be right.  Let me give a counterexample:

Consider this part of a CL program:

CL-USER(1): (defun foo (x)
              (declare (optimize speed (safety 0) (debug 0))
                       (fixnum x)
)
              (bar (the fixnum (1+ x))))
FOO
CL-USER(2): 

This is of course not a complete program, because the function BAR is
not yet defined.  If I try to run it, it will of course get an error.
But does that require then that I call this a "partly wrong" program?
No, of course not; it is not a program; it is a function, and for my
purposes it is completely correct (I've debugged it already :-)

So what can we do with an incomplete program?  Nothing?  Well, no.  Of
course, we can't run it (or can we?).  But even before talking about
running the program, we can at least do some reasoning about it:

 1. It seems to have some static type declarations (in a dynamic
langiuage?  oh my :-).

 2. The 1+ operation limits the kinds of operations that would be
acceptable, even in the absence of static type declarations.

 3. The declarations can be ignored by the CL implementation, so #2
might indeed come into play.  I ensured that the declarations would
be "trusted" in Allegro CL, by declaring high speed and low safety.

 4. I can compile this function.  Note that I get a warning about the
incompleteness of the program:

CL-USER(2): (compile 'foo)
Warning: While compiling these undefined functions were referenced: BAR.
FOO
NIL
NIL
CL-USER(3): 

 5. I can disassemble the function.  Note that it is a complete
function, despite the fact that BAR is undefined:

CL-USER(3): (disassemble 'foo)
;; disassembly of #<Function FOO>
;; formals: X
;; constant vector:
0: BAR

;; code start: #x406f07ec:
   0: 83 c0 04 addl	eax,$4
   3: 8b 5e 12 movl	ebx,[esi+18]  ; BAR
   6: b1 01     movb	cl,$1
   8: ff e7     jmp	*edi
CL-USER(4): 

 6. I can _even_ run the program!  Yes, I get an error:

CL-USER(4): (foo 10)
Error: attempt to call `BAR' which is an undefined function.
  [condition type: UNDEFINED-FUNCTION]

Restart actions (select using :continue):
 0: Try calling BAR again.
 1: Return a value instead of calling BAR.
 2: Try calling a function other than BAR.
 3: Setf the symbol-function of BAR and call it again.
 4: Return to Top Level (an "abort" restart).
 5: Abort entirely from this (lisp) process.
[1] CL-USER(5): 

 7. But note first that I have many options, including retrying the
call to BAR.  What was this call to BAR?  I can reason about that as
well, by getting a backtrace:

[1] CL-USER(5): :zoom
Evaluation stack:

   (ERROR #<UNDEFINED-FUNCTION @ #x406f3dfa>)
 ->(BAR 11)
   [... EXCL::%EVAL ]
   (EVAL (FOO 10))
   (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)
   (TPL:START-INTERACTIVE-TOP-LEVEL
      #<TERMINAL-SIMPLE-STREAM [initial terminal io] fd 0/1 @ #x40120e5a>
      #<Function TOP-LEVEL-READ-EVAL-PRINT-LOOP> ...)
[1] CL-USER(6): 

 8. If I then define BAR, with or without compiling it, and then take
that option to retry the call:

[1] CL-USER(6): (defun bar (x) (1- x))
BAR
[1] CL-USER(7): :cont
10
CL-USER(8): 

Hmm, I was able to complete the program dynamically?  Who ever heard
of doing that? :-)

> During development, I frequently sprinkle my (Haskell) program with
> 'undefined' and 'error "implement later"' - which then lets me run the
> implemented parts until execution hits one of the 'undefined's.

Why do it manually?  And what do you do when you've hit the undefined?
Start the program over?

> The "cost" of static typing for running an incomplete program is thus
> simply that I have to name entities I refer to.  I'd argue that this
> is good practice anyway, since it's easy to see what remains to be
> done.

Good practice, yes, but why not have the language help you to practice
it?

-- 
Duane Rettig    duane at franz.com    Franz Inc.  http://www.franz.com/
555 12th St., Suite 1450               http://www.555citycenter.com/
Oakland, Ca. 94607        Phone: (510) 452-2000; Fax: (510) 452-0182   



More information about the Python-list mailing list