469,326 Members | 1,351 Online
Bytes | Developer Community
New Post

Home Posts Topics Members FAQ

Post your question to a community of 469,326 developers. It's quick & easy.

Python from Wise Guy's Viewpoint

THE GOOD:

1. pickle

2. simplicity and uniformity

3. big library (bigger would be even better)

THE BAD:

1. f(x,y,z) sucks. f x y z would be much easier to type (see Haskell)
90% of the code is function applictions. Why not make it convenient?

2. Statements vs Expressions business is very dumb. Try writing
a = if x :
y
else: z

3. no multimethods (why? Guido did not know Lisp, so he did not know
about them) You now have to suffer from visitor patterns, etc. like
lowly Java monkeys.

4. splintering of the language: you have the inefficient main language,
and you have a different dialect being developed that needs type
declarations. Why not allow type declarations in the main language
instead as an option (Lisp does it)

5. Why do you need "def" ? In Haskell, you'd write
square x = x * x

6. Requiring "return" is also dumb (see #5)

7. Syntax and semantics of "lambda" should be identical to
function definitions (for simplicity and uniformity)

8. Can you undefine a function, value, class or unimport a module?
(If the answer is no to any of these questions, Python is simply
not interactive enough)

9. Syntax for arrays is also bad [a (b c d) e f] would be better
than [a, b(c,d), e, f]

420

P.S. If someone can forward this to python-dev, you can probably save some
people a lot of soul-searching
Jul 18 '05
467 18282
Fergus Henderson wrote:
Pascal Costanza <co******@web.de> writes:

- The important thing here is that the EMLOYED mixin works on any class,
even one that is added later on to a running program. So even if you
want to hire martians some time in the future you can still do this.

What happens if the existing class defines a slot named "salary" or "company",
but with a different meaning? Are slot names global, or is there some sort
of namespace control to prevent this kind of accidental name capture?


Sure, that's what Common Lisp's packages are there for. Define the
EMPLOYED mixin in its own package - done! You won't ever need to worry
about name clashes.
Anyway, regarding how to write this example in a statically typed
language: you can do this in a quite straight-forward manner,
by just keeping a separate table of employees.
Yuck.
static void test_employed() {
class Person {
public String name;
Person(String n) { name = n; }
};
Person joe = new Person("joe");
System.out.println("-> hire joe");
hire(joe, 60000);
System.out.println("name: " + joe.name);
System.out.println("class: "
+ joe.getClass().getName());
Employee e = (Employee) employees.get(joe); ^^^^^^^^^^^^^^^^^^
This part is not domain-specific, but shows that your abstraction leaks.
I.e. the client of your interface has to remember how the employee
abstraction is implemented in order to use it correctly.

In my original example, I was able to just call (company joe) and
(salary joe) (or, in Java syntax, this would be joe.salary and
joe.company). I.e., I don't have to know anything about the internal
implementation.

You can't implement unanticipated optional features in a statically
typed language that doesn't involve leaking abstractions.
As you can see, there's no need here for dynamically changing the types of
objects at runtime or for creating classes at runtime. But you can employ
Martians or any other object.
Sure. You also don't need functions and parameter passing. You also
don't need GOSUB and RETURN. So why don't we just program in assembler
again?
This example makes use of one dynamic cast; that's because the Java
type system doesn't support generics / parametric polymorphism. It would
be a little nicer to do this in a language which supported generics, then
we could use `Hashtable<Object, Employee>' rather than just `Hashtable',
and there wouldn't be any need for the dynamic cast to `(Employee)'.


I would still need to remember what features happen to be kept external
from my objects and what not.
Pascal

P.S.: Your implementation of default_company doesn't match mine. Yours
is not dynamically scoped. But maybe that's nitpicking...

Jul 18 '05 #451
Fergus Henderson <fj*@cs.mu.oz.au> writes:
Anyway, regarding how to write this example in a statically typed
language: you can do this in a quite straight-forward manner,
by just keeping a separate table of employees.
For example, here it is in Java.


And in case you didn't like the type declarations and downcast that you
need in Java, here it is in Mercury.

:- module employed.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.
:- import_module map, string, std_util.

default_company = "constanz-inc".

:- type employee ---> some [Obj]
employee(object::Obj, salary::int, company::string).

hire(Obj, Salary, !Employees) :-
hire(Obj, Salary, default_company, !Employees).
hire(Obj, Salary, Company, !Employees) :-
set(!.Employees, Obj, 'new employee'(Obj, Salary, Company),
!:Employees).
fire(Obj, !Employees) :-
delete(!.Employees, Obj, !:Employees).

:- type person ---> person(name::string).

test_employed(!.Employees) -->
{ Joe = person("joe") },
print("-> hire joe"), nl,
{ hire(Joe, 60000, !Employees) },
print("name: " ++ Joe^name), nl,
print("class: " ++ type_name(type_of(Joe))), nl,
print("employed: " ++ (if !.Employees `contains` Joe
then "yes" else "no")), nl,
print("company: " ++
!.Employees^det_elem(Joe)^company), nl,
print("salary: "),
print(!.Employees^det_elem(Joe)^salary), nl,
print("-> fire joe"), nl,
{ fire(Joe, !Employees) },
(if {!.Employees `contains` Joe} then
print("joe is still employed."), nl
else
print("joe is not employed anymore."), nl
).

main --> { init(Employees) }, test_employed(Employees).

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #452
Fergus Henderson wrote:
Fergus Henderson <fj*@cs.mu.oz.au> writes:

Anyway, regarding how to write this example in a statically typed
language: you can do this in a quite straight-forward manner,
by just keeping a separate table of employees.
For example, here it is in Java.

And in case you didn't like the type declarations and downcast that you
need in Java, here it is in Mercury.


Thanks for the variations on this theme. However, your abstraction is
still leaking.
print("employed: " ++ (if !.Employees `contains` Joe ^^^^^^^^^^^^^^^^^^^^^^ then "yes" else "no")), nl,
print("company: " ++
!.Employees^det_elem(Joe)^company), nl, ^^^^^^^^^^^^^^^^^^^^^^^^^ print("salary: "),
print(!.Employees^det_elem(Joe)^salary), nl,

^^^^^^^^^^^^^^^^^^^^^^^^^

Pascal

Jul 18 '05 #453
Pascal Costanza <co******@web.de> writes:
Fergus Henderson wrote:
static void test_employed() {
class Person {
public String name;
Person(String n) { name = n; }
};
Person joe = new Person("joe");
System.out.println("-> hire joe");
hire(joe, 60000);
System.out.println("name: " + joe.name);
System.out.println("class: "
+ joe.getClass().getName());
Employee e = (Employee) employees.get(joe); ^^^^^^^^^^^^^^^^^^
This part is not domain-specific, but shows that your abstraction leaks.
I.e. the client of your interface has to remember how the employee
abstraction is implemented in order to use it correctly.

In my original example, I was able to just call (company joe) and
(salary joe) (or, in Java syntax, this would be joe.salary and
joe.company). I.e., I don't have to know anything about the internal
implementation.


Well, you have a point, I didn't encapsulate the use of the Hashtable.
I can do that quite easily:

static Employee employee(Object obj) {
return (Employee) employees.get(obj);
}

Then the code there could become as follows.

System.out.println("employed: " +
(employee(joe) != null ? "yes" : "no"));
System.out.println("company: " + employee(joe).company);
System.out.println("salary: " + employee(joe).salary);

If you prefer, you can make make it simpler still,

System.out.println("employed: " +
(employeed(joe) ? "yes" : "no"));
System.out.println("company: " + company(joe));
System.out.println("salary: " + salary(joe));

by defining suitable methods:

static bool employed(Object obj) {
return employee(obj) != null;
}
static String company(Object obj) {
return employee(obj).company;
}
static int salary(Object obj) {
return employee(obj).salary;
}

Now, my guess is that you're still going to be complaining about the
abstraction leaking, but I don't think such complaints are valid.
Yes, the syntax is different than a field access, but that's not
important. The client is going to need to know the names of the
attributes or methods they want to use anyway; as long as they need to
know whether the name of the entity is "wage" or "salary", it doesn't make
a significant difference that they also need to know whether the interface
to that entity is a field, a member function, or a static function.

And of course, this syntax issue is language-specific. In Mercury
and Haskell, the same syntax is used for field access, method call,
and function call, so the issue doesn't arise!
You can't implement unanticipated optional features in a statically
typed language that doesn't involve leaking abstractions.
Not true. See above.
I would still need to remember what features happen to be kept external
from my objects and what not.


No, that's not what you need to remember. You just need to remember the
names of the features, and for each feature what kind of feature it is:
whether it is a field, an instance method, or a static method.
If it is a field, you know it is kept inside the object, but in
the other two cases the implementation is encapsulated -- the user
can't tell where the data is stored.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #454
pr***********@comcast.net wrote:
Joachim Durchholz <jo***************@web.de> writes:
I'm still hoping for an explanation on the practical relevance of that
black hole device.

Neel Krishnaswami had a wonderful explanation in article
<sl******************@gs3106.sp.cs.cmu.edu>


And note that his example for practical relevance includes a datatype.

Haskell doesn't support recursive types directly, but a recursive
datatype for lists is easy:

data List a = Nil | Cons a (List a)

Since Haskell is lazy, that's already a lazy list, but even if Haskell
was eager, you could write a similar datatype for lazy lists.
(And of course Haskell lists are essentially just given by the above
datatype, together with some syntactic sugar).

It's very natural for a relevant application of recursive types to
be coupled with data.

- Dirk


Jul 18 '05 #455
On 10/20/2003 5:49 AM, Kenny Tilton wrote:


Dennis Lee Bieber wrote:
Just check the archives for comp.lang.ada and Ariane-5.

Short version: The software performed correctly, to
specification (including the failure mode) -- ON THE ARIANE 4 FOR
WHICH IT WAS DESIGNED.

Nonsense. From: http://www.sp.ph.ic.ac.uk/Cluster/report.html

"The internal SRI software exception was caused during execution of a
data conversion from 64-bit floating point to 16-bit signed integer

[...]


LISP wouldn't have helped -- since the A-4 code was supposed
to failure with values that large... And would have done the same
thing if plugged in the A-5. (Or are you proposing that the A-4 code
is supposed to ignore a performance requirement?)
"supposed to" fail? chya. This was nothing more than an unhandled
exception crashing the sytem and its identical backup. Other conversions
were protected so they could handle things intelligently, this bad boy
went unguarded. Note also that the code functionality was pre-ignition
only, so there is no way they were thinking that a cool way to abort the
flight would be to leave a program exception unhandled.

What happened (aside from an unnecessary chunk of code running
increasing risk to no good end) is that the extra power of the A5 caused
oscillations greater than those seen in the A4. Those greater
oscillations took the 64-bit float beyond what would fit in the 16-bit
int. kablam. Operand Error. This is not a system saying "whoa, out of
range, abort".

"To determine the vulnerability of unprotected code, an analysis was performed
on every operation which could give rise to an exception, including an Operand
Error. [...] It is important to note that the decision to protect certain
variables but not others was taken jointly by project partners at several
contractual levels."

"There is no evidence that any trajectory data were used to analyse the
behaviour of the unprotected variables, and it is even more important to note
that it was jointly agreed not to include the Ariane 5 trajectory data in the
SRI requirements and specification."
"It was the decision to cease the processor operation which finally proved
fatal. Restart is not feasible since attitude is too difficult to re-calculate
after a processor shutdown; therefore the Inertial Reference System becomes
useless. The reason behind this drastic action lies in the culture within the
Ariane programme of only addressing random hardware failures. From this point of
view exception - or error - handling mechanisms are designed for a random
hardware failure which can quite rationally be handled by a backup system."
As for Lisp not helping:


"It has been stated to the Board that not all the conversions were protected
because a maximum workload target of 80% had been set for the SRI computer"

CB
Jul 18 '05 #456
Pascal Costanza <co******@web.de> writes:
OK, let's try to distill this to some simple questions.

Assume you have a compiler ML->CL that translates an arbitrary ML
program with a main function into Common Lisp. The main function is a
distinguished function that starts the program (similar to main in C).
The result is a Common Lisp program that behaves exactly like its ML
counterpart, including the fact that it doesn't throw any type errors at
runtime.

Assume furthermore that ML->CL retains the explicit type annotations in
the result of the translation in the form of comments, so that another
compiler CL->ML can fully reconstruct the original ML program without
manual help.

Now we can modify the result of ML->CL for any ML program as follows. We
add a new function that is defined as follows:

(defun new-main ()
(loop (print (eval (read)))))

(We assume that NEW-MAIN is a name that isn't defined in the rest of the
original program. Otherwise, it's easy to automatically generate a
different unique name.)

Note that we haven't written an interpreter/compiler by ourselves here,
we just use what the language offers by default.

Furthermore, we add the following to the program: We write a function
RUN (again a unique name) that spawns two threads. The first thread
starts the original main function, the second thread opens a console
window and starts NEW-MAIN.

Now, RUN is a function that executes the original ML program (as
translated by ML->CL, with the same semantics, including the fact that
it doesn't throw any runtime type errors in its form as generated by
ML->CL), but furthermore executes a read-eval-print-loop that allows
modification of the internals of that original program in arbitrary
ways. For example, the console allows you to use DEFUN to redefine an
arbitrary function of the original program that runs in the first
thread, so that the original definition is not visible anymore and all
calls to the original definiton within the first thread use the new
definition after the redefinition is completed. [1]

Now here come the questions.

Is it possible to modify CL->ML in a way that any program originally
written in ML, translated with ML->CL, and then modified as sketched
above (including NEW-MAIN and RUN) can be translated back to ML?
Yes, it is possible. For example, have CL->ML ignore the type annotation
comments, and translate CL values into a single ML discriminated union type
that can represent all CL values. Or (better, but not quite the same semantics)
translate those CL values with ML type annotations back to corresponding ML
types, and insert conversions to convert between the generic CL type and
other specific types where appropriate.

Suppose the original ML program defines the following functions

foo : int -> int
bar : string -> string
...

We can add dynamic typing like this:

datatype Generic = Int of int
| String of string
| Atom of string
| Cons Generic Generic
| Apply Generic Generic
| ...

fun foo_wrapper (Int x) = (Int (foo x))

fun bar_wrapper (String x) (String (foo x))

and then dynamic binding like this

val foo_binding = ref foo_wrapper

val bar_binding = ref bar_wrapper

For dynamic binding, function calls will have to be translated to does
an indirection. So a call

let y = foo x

will become

let y = !foo_binding x

or, if x and y are used in a way that requires that they have type int,
then

let (Int y) = !foo_binding (Int x)

We can then simulate eval using an explicit symbol table.

fun lookup "foo" = !foo_binding
| lookup "bar" = !bar_binding
| lookup "define" = !define_binding
...

fun define "foo" f = foo_binding := f
| define "bar" f = bar_binding := f
| define "define" f = define_binding := f
...

fun define_wrapper (Cons (Atom name) body) =
let () = define name body in Atom "()"
val define_binding = ref define_wrapper

fun eval (Apply func arg) =
case (eval func) of
Atom funcname => (lookup funcname) (eval arg)
| eval x = x

Note that our symbol table includes an entry for the function "define",
so that eval can be used to modify the dynamic bindings.

The rest (e.g. read) is straight-forward.
To ask the question in more detail:

a) Is it possible to write CL->ML in a way that the result is both still
statically type checkable
Yes, but only in a weak sense. Since we are allowing dynamic binding,
and we want to be able to dynamically bind symbols to a different type,
we're definitely going to have the possibility of dynamic type errors.
The way this is resolved is that things which would have been type errors
in the original ML program may become data errors (invalid constructor
in an algebraic type Generic) in the final ML program.
and not considerably larger than the original program that was given to
ML->CL.
There's a little bit of overhead for the wrapper functions of type
"Generic -> Generic", and for the variables of type "ref (Generic -> Generic)"
which store the dynamic bindings. It's about two lines of code per
function in the original program. I don't think this is excessive.
Especially, is it possible to do this
without implementing a new interpreter/compiler on top of ML and then
letting the program run in that language, but keep the program
executable in ML itself?
The program includes a definition for "eval", and "eval" is an
interpreter, So in that sense, we have added a new interpreter.
But the bulk of the program is written in ML, without making use of eval.
c) If you respond with yes to either a or b, what does your sketch of an
informal proof in your head look like that convincingly shows that this
can actually work?


See above.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #457
Fergus Henderson wrote:
Suppose the original ML program defines the following functions

foo : int -> int
bar : string -> string
...

We can add dynamic typing like this:

datatype Generic = Int of int
| String of string
| Atom of string
| Cons Generic Generic
| Apply Generic Generic
| ... ^^^
How many do you need of those, especially when you want to allow that
type to be extended in the running program?
Note that our symbol table includes an entry for the function "define",
so that eval can be used to modify the dynamic bindings.
DEFUN is just one example. What about DEFTYPE, DEFCLASS, DEFPACKAGE, and
so forth...
The program includes a definition for "eval", and "eval" is an
interpreter, So in that sense, we have added a new interpreter.


That's the whole point of my argument.
Pascal

--
Pascal Costanza University of Bonn
mailto:co******@web.de Institute of Computer Science III
http://www.pascalcostanza.de Römerstr. 164, D-53117 Bonn (Germany)

Jul 18 '05 #458
Pascal Costanza <co******@web.de> writes:
The program includes a definition for "eval", and "eval" is an
interpreter, So in that sense, we have added a new interpreter.


That's the whole point of my argument.


Then it's a pretty silly argument.

You ask if we can implement eval, which is an interpreter,
without including an interpreter?
I don't see what you could usefully conclude from the answer.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #459
Pascal Costanza <co******@web.de> writes:
Fergus Henderson wrote:
Suppose the original ML program defines the following functions

foo : int -> int
bar : string -> string
...

We can add dynamic typing like this:

datatype Generic = Int of int
| String of string
| Atom of string
| Cons Generic Generic
| Apply Generic Generic
| ...

^^^
How many do you need of those, especially when you want to allow that
type to be extended in the running program?


In Haskell you would just use "Dynamic" instead of the above, and be done
with it. In Mercury, you'd just use "univ".

In SML, I don't know. Probably none. In fact, the first two entries in
the type above are not really necessary, you could just represent ints
and strings as Atoms. Even Apply could be represented using just Cons
and Atom: instead of Apply x y, we could use Cons (Atom "apply") (Cons x y).
Note that our symbol table includes an entry for the function "define",
so that eval can be used to modify the dynamic bindings.


DEFUN is just one example. What about DEFTYPE, DEFCLASS, DEFPACKAGE, and
so forth...


Well, DEFTYPE in lisp really just defines a function, doesn't it?
So that's not much different than DEFUN. Likewise, DEFCLASS just
defines a collection of functions, doesn't it? OK, maybe these
also record some extra information in some global symbol tables.
That is easily emulated if you really want.

I don't know exactly what DEFPACKAGE does, but if the others are any
guide, it's probably not too hard either.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #460
Fergus Henderson wrote:
Pascal Costanza <co******@web.de> writes:

The program includes a definition for "eval", and "eval" is an
interpreter, So in that sense, we have added a new interpreter.
That's the whole point of my argument.

Then it's a pretty silly argument.

You ask if we can implement eval, which is an interpreter,
without including an interpreter?


Right.
I don't see what you could usefully conclude from the answer.


....that you can't statically type check your code as soon as you
incorporate an interpreter/compiler into your program that can interact
with and change your program at runtime in arbitrary ways.
Pascal

--
Pascal Costanza University of Bonn
mailto:co******@web.de Institute of Computer Science III
http://www.pascalcostanza.de Römerstr. 164, D-53117 Bonn (Germany)

Jul 18 '05 #461
Fergus Henderson wrote:
Pascal Costanza <co******@web.de> writes:

DEFUN is just one example. What about DEFTYPE, DEFCLASS, DEFPACKAGE, and
so forth...


Well, DEFTYPE in lisp really just defines a function, doesn't it?
So that's not much different than DEFUN. Likewise, DEFCLASS just
defines a collection of functions, doesn't it? OK, maybe these
also record some extra information in some global symbol tables.
That is easily emulated if you really want.

I don't know exactly what DEFPACKAGE does, but if the others are any
guide, it's probably not too hard either.


I am not sure if I understand you correctly, but are you actually
suggesting that it is better to reimplement Common Lisp on your own than
to just use one of the various Common Lisp implementations?

All I am trying to get across is that, in case you need the flexibility
a dynamic language provides by default, and only occasionally need to
restrict that flexibility, it's better to use a dynamic language from
the outset. The price to pay is that you cannot make use of a 100%
strict static type system anymore, but on the other hand you can pick
one of the stable Common Lisp implementations with language features
that have proven to work well during the past few decades.

Sure, if you don't need the flexibility of a dynamic language then you
can think about using a static language that might buy you some
advantages wrt to static checkability. But I highly doubt that this is a
rational choice because I don't know any empirical studies that show
that the problems that static languages intend to solve are in fact
problems that occur in practice.

Let's inspect the list of those problems again:

a) performance

Good Lisp/Scheme implementations don't have problems in this regard.

b) documentation

Documentation can be handled well with comments and well-chosen names.

c) absence of a certain class of bugs

It's not clear whether this class of bugs really occurs in practice.
There are also indications that these relatively trivial bugs are also
covered by test suites as soon as they consist of a reasonable number of
test cases.

d) unbreakable abstraction boundaries

Such boundaries seem to be as tedious to implement in dynamic languages,
as is the case for dynamic features in static languages.

My conclusions would be as follows:

a) and b) are relatively uninteresting. c) needs convincing studies from
the proponents of static type systems. As long as they don't provide
them, it's just guesswork that these bugs are really important.

d) is the hard part. Proponents of static languages say that it's
important to be able to express such boundaries because it increases
their expressive power. (That's one thing I have learned from this
discussion: It is arguable that this can also be an important kind of
expressive power.) Proponents of dynamic languages say that it's more
important to be able to work around restrictions, no matter whether they
are intentional or not; it's better not to be able to paint yourself
into a corner.

With regard to d, both "groups" don't have enough objective empirical
evidence beyond their own subjective experiences. Static programmers
don't have enough objective empirical evidence that their languages
objectively increase the quality of their software, and dynamic
programmers don't have enough objective empirical evidence that painting
oneself into corners is a problem that occurs regularly.

So both views essentially boil down to be no more than subjective belief
systems. And IMHO that's not so far from the "truth": I am convinced
that these issues depend mostly on personal programming style and
preferences and not so much on technological issues. Software quality is
a social construct, and social problems can hardly be solved with
technical means. If you have a bunch of good programmers and let them
choose their preferred tools, it's more likely that they produce good
software than when you have a bunch of average programmers and tell them
what tools they must use.

(It's very important in this regard that we are not talking about
braindead languages. There are both extremely stupid as well as
excellent exemplars of static and dynamic languages.)
Pascal

--
Pascal Costanza University of Bonn
mailto:co******@web.de Institute of Computer Science III
http://www.pascalcostanza.de Römerstr. 164, D-53117 Bonn (Germany)

Jul 18 '05 #462
Pascal Costanza <co******@web.de> writes:
I am not sure if I understand you correctly, but are you actually
suggesting that it is better to reimplement Common Lisp on your own than
to just use one of the various Common Lisp implementations?


No. I'm just pointing out that these sort of things can be implemented
in statically typed languages without much difficulty. Generally
I _don't_ want to reimplement Common Lisp. If on occaision I do need
some dynamic binding, or dynamic typing, say, then this need is usually
localized within a small part of the application, and so I can implement
what I need very easily.

Your article that I was responding to was suggesting that there might
be some things which could not be done in statically typed languages,
and in particular that this sort of eval(read()) loop might be one of them.
As I hope I've demonstrated, it is not.

So in answer to question "d" in that post of yours, "would you still
disagree with the assessment there is a class of programs that can be
implemented with dynamically typed languages but without statically
typed ones?", I would say yes, I still disagree.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #463
Pascal Costanza <co******@web.de> writes:
b) documentation

Documentation can be handled well with comments and well-chosen names.


Certainly it _can_ be, but keeping good, up-to-date documentation is a
difficult task. In my experience, average programmers do a better job
of documentation in languages which encourage explicitly declaring the
types of interfaces than in those which do not.

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #464
Fergus Henderson <fj*@cs.mu.oz.au> writes:
Well, DEFTYPE in lisp really just defines a function, doesn't it?
So that's not much different than DEFUN. Likewise, DEFCLASS just
defines a collection of functions, doesn't it? OK, maybe these
also record some extra information in some global symbol tables.
That is easily emulated if you really want.

I don't know exactly what DEFPACKAGE does, but if the others are any
guide, it's probably not too hard either.


Yes, if you reimplement Lisp you can achieve what was asked. However,
I don't think "turing equivalence" or Greenspun's tenth was the point...

/Jon

Jul 18 '05 #465
Fergus Henderson wrote:
Your article that I was responding to was suggesting that there might
be some things which could not be done in statically typed languages,
and in particular that this sort of eval(read()) loop might be one of them.
As I hope I've demonstrated, it is not.


And you're right in this regard. My statement was too strong. Of course
it is always possible to reimplement a dynamic language on top of a
static one and by this get the full expressive power of a dynamic
language. But I didn't have Turing equivalence in mind when I made that
statement. The real question is how hard it is to reimplement a dynamic
language, and wouldn't it be a better idea to use a dynamic language
when the requirements are of the sort that, when in doubt, flexibility
turns out to be more important than stacity.
Pascal

--
Pascal Costanza University of Bonn
mailto:co******@web.de Institute of Computer Science III
http://www.pascalcostanza.de Römerstr. 164, D-53117 Bonn (Germany)

Jul 18 '05 #466
In article <bo**********@f1node01.rhrz.uni-bonn.de>, Pascal Costanza wrote:
Fergus Henderson wrote:
Suppose the original ML program defines the following functions

foo : int -> int
bar : string -> string
...

We can add dynamic typing like this:

datatype Generic = Int of int
| String of string
| Atom of string
| Cons Generic Generic
| Apply Generic Generic
| ...

^^^
How many do you need of those, especially when you want to allow that
type to be extended in the running program?


Two more.

datatype generic = ...
| Class of generic ref
| Object of generic array ref

Then you can write a typeof function that looks at tags to decide what
to do.

This shouldn't be that hard to see: an implementation of Scheme or
Lisp doesn't require an infinite family of tags in the lower-level
implementation.
--
Neel Krishnaswami
ne***@cs.cmu.edu
Jul 18 '05 #467
Pascal Costanza <co******@web.de> writes:
Fergus Henderson wrote:
Pascal Costanza <co******@web.de> writes:
Fergus Henderson wrote:
The program includes a definition for "eval", and "eval" is an
interpreter, So in that sense, we have added a new interpreter.

That's the whole point of my argument.


Then it's a pretty silly argument.

You ask if we can implement eval, which is an interpreter,
without including an interpreter?


Right.


Then the answer is obviously no -- regardless of which language you use,
and whether it is statically typed or not. In some cases the interpreter
might be included as part of the standard library or even as a builtin-in
language feature, in other cases it may need to be written as part of the
program, but either way, it will still need to be included.
I don't see what you could usefully conclude from the answer.


...that you can't statically type check your code as soon as you
incorporate an interpreter/compiler into your program that can interact
with and change your program at runtime in arbitrary ways.


This conclusion doesn't follow. How could it, since neither the question
nor the answer made any reference to static type checking?

--
Fergus Henderson <fj*@cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
Jul 18 '05 #468

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

4 posts views Thread by Berco Beute | last post: by
reply views Thread by Luke Kenneth Casson Leighton | last post: by
reply views Thread by zhoujie | last post: by
reply views Thread by suresh191 | last post: by
reply views Thread by harlem98 | last post: by
reply views Thread by listenups61195 | last post: by
By using this site, you agree to our Privacy Policy and Terms of Use.