Re: RAD vs. performance



I'm responding to a whole lot of messages all at once here; so if you're
getting confused about who I'm replying to, it's all my fault. ;-)

On Wed, Jun 28, 2006 at 11:45:57AM +1000, cr88192 wrote:

"Jon Harrop" <usenet@xxxxxxxxxxxxxx> wrote in message
news:44a1b80c$0$69403$ed2619ec@xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Curtis W wrote:
Jon Harrop wrote:
<snip>


Unfortunately, said solution is fairly rigid when it comes to extending
code.

Which is ok because ints and floats have different semantics anyway.


I thought the semantics we had were for numbers, and ints and floats are
just hacks we use to get computers to work with them. To me, it seems
that _ideally_, as a programmer, you wouldn't have to worry about
whether your numbers are integers or floats or bignums or rationals or
complex numbers or whatever else.

about the only really hairy case I can think of is division, where:
int/int => int|float
which is not so good for static checks.

And not so good for correctness, either. Better would be:

rational / rational => rational

but then, why not

complex / complex => complex?

It can be done (Mathematica does it, I think) ...

def command1(x):
...

def command2(x, y):
...

commands = {
"c1": command1,
"c2": command2
}

command_name, args = get_command_from_user()
commands[command_name](*args)
---
As you can see, it would be impossible to infer whether or not the
number of arguments is valid for the given command at compile time.
However, because python is dynamically typed, it lets it slide and will
tell you at runtime if an error does occur. In a statically typed
language, you have to tell the compiler somehow that you know that
statement will not produce an error--usually with some sort of cast.

I agree that this is a case that dynamically typed languages tend to
handle more elegantly than statically typed ones. However, it is also a
case where type safety is out of the window, and I think it is a Good
Thing if the compiler doesn't let you get away with this.

On the other hand, if you did something like the following (assuming
command1 and command2 defined as before, and a lot of Common Lisp
features mixed in):

defmacro make_commands(xs):
expr :list (loop for x in xs collecting (expr x.str :: (list x x.arity)))

commands = make_commands(command1, command2)

command_name, args = get_command_from_user()
if commands[command_name].arity = args.length
commands[command_name](*args)
else
# scream bloody murder

The idea here being that make_commands will return a list that looks
like {
"command1": [command1, 1],
"command2": [command2, 2]
}

where the arity is automatically looked up and inserted so that it is
always correct. The code checks if the right number of arguments were
supplied. So nothing can go wrong (assuming for the moment that the
types of the arguments aren't a problem; a similar trick could be used
to ensure that, but let's not make things too complicated).

Still, most static type checkers would reject the code, because command1
and command2 don't have the same type, and hence cannot be in the same
list, without more magic being done. As, for example, in the code that
Jon gives:

# let exec name args =
let command1 = function `A x -> `T1 x | _ -> invalid_arg "" in
let command2 = function `B (x, y) -> `T2 (y, x) | _ -> invalid_arg ""
in
let commands = ["c1", command1; "c2", command2] in
(List.assoc name commands) args;;

which basically uses a lot of ugly syntax to make command1 and command2
have the same type, thus allowing them to be inserted in the same list
without tags (tagged unions would be an alternative).

If you want an efficient compiler there's type inference ;) A well
written compiler for a dynamic language isn't inherently slower than
any other type of compiler,

There is an overwhelming amount of quantitative evidence to the
contrary.

That's irrelevant, considering I was speaking theoretically. It's
completely possible to write a fast, dynamically typed language.

By sacrificing compilation speed and partial recompilation to an extent so
impractical that virtually nobody does this.

You are aware that the Perl community, everybody who writes Scheme
compilers, all these folks working on compilers for Python and Ruby,
etc. etc. are working on getting decent performance out of dynamically
typed languages, are you?

You are also aware that if a language has the same types and operations
as Ocaml (e.g. ints and floats using + and +.), then doing type
inference (for purposes of generating efficient code) is going to be
exactly the same for a dynamically typed language as it is for Ocaml?
And that the only cases in which you wouldn't be able to infer the type,
and thus would fall back to boxing and run-time dispatch, is when Ocaml
would reject your program?

Really, the difference between static and dynamic typing isn't that
dramatic. The difference is that, for static typing, you reject programs
when you aren't 100% certain that the types are good, whereas in dynamic
typing you assume they are until you are 100% sure they aren't (which
you find out at run time). In cases where you are 100% sure the types
are correct at compile time, there needn't be a difference.

What makes efficient code generation hard for many dynamically typed
languages is the semantics of these languages: the fact that, for
example, fixnums and flonums and bignums are all the same semantically.
Thus, you can have a situation where a + b doesn't fit in a 31-bit
integer, and you thus have to convert to using bignums, or a / b doesn't
return an integer, and you have to use rationals instead. This means
that you can't use fixnums to store these values, no matter if your
language is statically or dynamically typed.

Using a decent type inferencer and code
analsys, it should be possible to produce an identical binary in most
situations.

Yes, using a compiler like Stalin that takes three orders of magnitude
longer to compile.

Surely you can think of other reasons why Stalin might be slow. Just
because you have _one_ example in which compilation takes a long time
using _one_ compiler for _one_ language doesn't mean all compilers for
all dynamic languages are going to be slow in all cases.

Perhaps, although that that would be nothing compared to the amount of
development time saved by emphasizing flexibility rather than speed.

On the contrary, it hinders development by slowing compile time and
failing
to catch errors statically. Dynamic typing offers no extra "flexibility"
precisely because it is a special case of static typing.

As always, it depends. Static typing is great when it helps you catch
errors, of no help when it doesn't catch errors, and a burden when it
rejects valid (as in: under a less restrictive type checker,
they would have compiled and run perfectly) programs. Dynamic typing is
great when it automatically inserts run-time type checks where they are
needed, of no help when it doesn't need to, and a burden when it inserts
unnecessary run-time checks.

Also observe that the burdens imposed are different: in case of static
type checking, the burden is that you have to do extra work to appease
the checker. With dynamic typing, you don't have to do extra work, but
your programs run slower than they should.

Finally, note that the benefit of the type checker catching errors is
not necessarily lost when using dynamic checking: the compiler could
still give you a warning when types don't (statically) match. This is
called "soft typing" (soft, because it doesn't outright reject your
program).

You could argue that dynamic typing offers brevity in cases where static
typing is of no benefit. However, there is little evidence to support such
a claim. Look at my OCaml translation above, for example.

It's definitely less elegant than the Python code.

I will argue the something here:
one of the main uses of dynamic typing, is the ability to have
generic/hetrogeneous containers.

As in the Python example. But, as Curtis points out: truly heterogenous
containers aren't actually of any use. A better claim to make would be:
many static type checkers are too restrictive.

My view is that the only sensible notion of type (from a correctness,
not performance) point of view is what interface it supports. This means
that, if you have a bunch of objects that can all be passed to a
function called foo, you can put these in a container together. Of
course, when they come out of the container, the only functions you can
safely call on them are the ones that can be called on every object you
put in the container; in this case, foo. So, a static type checker would
accept you passing these objects to foo, but reject everything else. It
turns out Ocaml actually supports this, except that you have to use
methods (object#foo ()) rather than functions (foo object), because
functions cannot be defined for more than one type (e.g. it would be
impossible to have foo :: int -> unit and foo :: float -> unit in the
same program).

some have argued templates as a solution to this, but imo they are horrid.
much better to just relax and say "oh well, I will put whatever I want in
these here variables".

And run the risk of applying a function to arguments it can't handle?

of course, most of the time it is easier to be like "ok, this variable will
only ever hold a single type", and thus use some fixed type.

And the same goes for containers. How often do you really want to put
objects of different types in the same container?

imo, the bigger problem right now (in my particular case), is that a lot of
time is going into the ref-counting functions (incrementing and decrementing
counts) yet still large numbers of cons cells are turning into garbage (at
present, my vm uses cons cells to temporarily hold function arguments, but
they are getting lost somewhere and not returning to the big list of free
conses).

I think you should really consider using a real garbage collector.
Reference counting is slow (as you've found out) and doesn't collect all
garbage (which might be the reason you're losing cons cells). Writing a
simple stop-and-copy collector is easy, and I believe they are still the
fastest collectors. At least you can get arbitrarily cheap garbage
collection by using larger heaps. ;-)

Bob

---
For every complex problem there is an answer that is clear, simple, and wrong.
-- H.L. Mencken

Attachment: pgpwQtmNuH29t.pgp
Description: PGP signature



Relevant Pages

  • Re: RAD vs. performance
    ... Thing if the compiler doesn't let you get away with this. ... command1 and command2 defined as before, and a lot of Common Lisp ... written compiler for a dynamic language isn't inherently slower than ... The difference is that, for static typing, you reject programs ...
    (comp.lang.misc)
  • Re: Obstacles for Tcl/Tk commercial application development ?
    ... The "error" is at a level that no compiler can catch. ... typing doesn't mean you have no type-induced errors in your code. ... language that uses strong static typing. ... On static type systems, this is no problem: ...
    (comp.lang.tcl)
  • Re: Use of script languages (Was: Tomcat user authentication question.)
    ... still wouldn't be a compiled language. ... for example one of the most dynamic language is LISP and there ... are very efficient compiler for LISP, I remember that, may 20 years ago, ... Static typing versus dynamic typing, ...
    (comp.os.vms)
  • Re: RAD vs. performance
    ... It depends on what the compiler can infer and optimize. ... rationals are another type, which are not supported in the cpu and would ... The difference is that, for static typing, you reject programs ... (rather than typed declarations based language with a class-based types ...
    (comp.lang.misc)
  • Re: "STL from the Ground Up"
    ... high-level intermediate language than can interoperate with many other ... If your language lacks expressive features then you cannot write code ... memory management in comparison. ... Mostly because type errors mean that the programmer and compiler disagree ...
    (comp.programming)