gudeman@cs.arizona.edu (David Gudeman) (03/12/91)
I have become convinced that many people still don't know what I mean by "dynamic typing". This is an indictment of computer science educations in general, that such an important and pervasive paradigm has been so neglected. Dynamic typing is not a new or strange way to program -- Lisp and SNOBOL are both nearly as old as Fortran, and they are dynamically typed languages. Mathematical notation is generally closer to dynamically typed languages than to statically typed languages. In fact, I'd go so far as to claim that it is static type checking that is the peculiar notion, not dynamic typing. Static typing originated, as near as I can determine, with low-level languages like Fortran an Algol that were little more than glorified assemblers. They had to give type declarations so that they could generate code that an assembly language programmer expected to be generated for expressions. Much later, static typing took on religious significance as an element of the great god Structured Programming, and became a living force. Now people have actually come to believe, against all evidence, that static typing is important for program reliability. Part of this belief is rooted in the confusion between weak typing and dynamic typing. The two are completely unrelated. In fact, I'd claim that most dynamicly typed languages are strongly typed in some sense. What is the essential difference between strong and weak typing? I claim that it is that weakly typed languages can do bizarre things due to type errors. For example if the length of an array is not included in the definition of the type, you can do arbitrary things to memory by setting out-of-bounds values in the array. Semantically we say that the behavior of the program becomes undefined due to a type error. If we ignore the idea of when type checking is done, we can define strong typing as follows: strong typing a language feature that guarantees that the behavior of a program never becomes undefined as a result of a type error. All dynamically typed language I know have this feature. In fact for most of these languages there are no programs that have undefined behavior, I don't know whether any statically typed language can make that claim. With static typing you need a great deal of information at compile time to be able to guarantee strong typing. This has two consequences: (1) you have to limit the forms of expressions to some set for which you know a type-checking decision procedure, and (2) you have to acquire type information somewhere. The first consequence is unacceptable in my view -- I don't like it when the set of things I can express are arbitrarily limited. The second consequence is also unacceptable if it means that the programer is burdened with the job of providing this information (and that is the case for the huge majority of statically, strongly typed languages). The computer is supposed to to busy work like checking type consistency, the programmer should no more be burdened with this than he should have to calculate constants. How would you like it if you could not rely on constant folding, so you had to calculate the values of all your constants? Yet this is the same sort of busy work as writing type declarations. I think that static typing took on religious significance as a result of the problems with weak typing -- people didn't understand that the two issues are orthogonal, and that there was an alternative to static typing. They that weak typing is a problem because it can lead to undefined behavior -- which means it can be very hard to debug. This is a reasonable position to take, however the solution they came up with was not reasonable. The solution was to require more and more restrictions to the set of legal expressions, and more and more declarations from the programmer. This was so burdensome that enormour amounts of further research went into finding ways to undo the unpleasant effects of these decisions. Thus we have 'typecase', polymorphic functions, polymorphic modules, virtual functions, etc., ad nauseum. Each feature adds semantic burden to the language, making it harder to learn and harder to implement. And you still don't get the full expressiveness of dynamic typing, which was the obvious solution to begin with. The only real problem with dynamic typing is that it is (slightly) less efficient that static typing. However, there are two obvious solutions to this problem, one of which I presented in part 2. The other is to start with a dynamically typed language and add optional type declarations. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
yodaiken@chelm.cs.umass.edu (victor yodaiken) (03/12/91)
In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >are dynamically typed languages. Mathematical notation is generally >closer to dynamically typed languages than to statically typed >languages. > >In fact, I'd go so far as to claim that it is static type checking >that is the peculiar notion, not dynamic typing. Static typing >originated, as near as I can determine, with low-level languages like >Fortran an Algol that were little more than glorified assemblers. >They had to give type declarations so that they could generate code >that an assembly language programmer expected to be generated for >expressions. Much later, static typing took on religious significance >as an element of the great god Structured Programming, and became a >living force. Now people have actually come to believe, against all >evidence, that static typing is important for program reliability. > When I write: let S be a set let f:S -> X let Y = f(S) and let Z = {g(s): for s IN Y} for each s in S let k_s = f(S) a human being could figure out that: X, Y and Z are also sets that by f(S) I mean to apply f to each element of S and that the last line makes more sense if we interpret f(S) as a typo that should really be f(s) How is a dynamically typed programming language going to figure this out?
peter@ficc.ferranti.com (Peter da Silva) (03/12/91)
In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > Now people have actually come to believe, against all > evidence, that static typing is important for program reliability. No, they believe that dynamic typing imposes a certain unavoidable runtime overhead, and aren't willing to pay that cost. As computers get faster this becomes more of a moot point outside the frantic world of embedded controllers and videogames. You can argue how great that cost is... and certainly when people are happy with the performance of X, Windows, and Multifinder it's pretty daft to quibble over less than a factor of 10 or so cost... but it's the real reason people are still working in C and other Algol-derived languages. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
gudeman@cs.arizona.edu (David Gudeman) (03/13/91)
In article <27794@dime.cs.umass.edu> victor yodaiken writes:
]When I write:
] let S be a set
] let f:S -> X
] let Y = f(S) and let Z = {g(s): for s IN Y}
] for each s in S let k_s = f(S)
]
]a human being could figure out that:
]... that the last line makes more sense if we interpret f(S) as a typo
]that should really be f(s)
A human is a lot smarter than a static type checker. A static type
checker can only tell you there is an inconsistency between the
declaration of f and its use. It doesn't know which is the error.
]How is a dynamically typed programming language going to figure this
]out?
A dynamic language might tell you more than the static language does,
given that you actually execute this code. During the execution of f
you will probably call some function on the argument that is only
defined for sets, and you will get an appropriate error message
telling you what value should be a set and isn't.
Why should a programming language be expected to catch this type of
error at compile time, and not some other class of errors? Why are
type errors so special? There are many other sorts of errors that
could be detected at compile time by requiring enough work from the
programmer. They aren't checked because it is rightly seen that the
extra work outweighs the potential benifits. The same is true of type
declarations, which is obvious to everyone who programs in a language
without them. Required declarations are not in languages because they
are a good idea, they are an accident of history.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
yodaiken@chelm.cs.umass.edu (victor yodaiken) (03/13/91)
In article <609@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <27794@dime.cs.umass.edu> victor yodaiken writes: >]When I write: >] let S be a set >] let f:S -> X >] let Y = f(S) and let Z = {g(s): for s IN Y} >] for each s in S let k_s = f(S) >] >]a human being could figure out that: >]... that the last line makes more sense if we interpret f(S) as a typo >]that should really be f(s) > >A human is a lot smarter than a static type checker. A static type >checker can only tell you there is an inconsistency between the >declaration of f and its use. It doesn't know which is the error. > >]How is a dynamically typed programming language going to figure this >]out? > >A dynamic language might tell you more than the static language does, >given that you actually execute this code. During the execution of f >you will probably call some function on the argument that is only >defined for sets, and you will get an appropriate error message >telling you what value should be a set and isn't. > I'm agnostic on this issue, but your argument is very unpersuaive. Won't a runtime type language attempt to do something sensible with the error, something that might obscure the error, but leave it lying around for later? If the language decides to apply f to each element of S,then I may get a perfectly reasonable value for my test data, but suffer from mysterious errors at some future date. I'll come up with a more worked out example if needed.
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/13/91)
In article <609@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > Why should a programming language be expected to catch this type of > error at compile time, and not some other class of errors? Why are > type errors so special? Compiler writers generally find it convenient to catch type errors. Programmers generally find it convenient to know the type of a variable and to have types always checked at compile time (rather than as an optimization that a few compilers might provide). Who are you to argue with taste? > Required declarations are not in languages because they > are a good idea, I have to disagree. Declarations help catch typos, if nothing else. ---Dan
aipdc@castle.ed.ac.uk (Paul Crowley) (03/13/91)
I'm going to split languages in two by type extensibility. In C and ML, you can explicitly make up a new type from old types: a widget is made up of two foos and a bar. In Logo, there are only three types: words, numbers, and lists. If you want to define an imaginary number, you could use a list of two integers. If you want to define a UNIX-style time as a list of two integers [secs, usecs], you can do that too. If you accidentally feed an imaginary number to a function that wants a date, the language won't say a word. Prolog behaves this way too. What are the words for these two? (I know that if you really wanted to, you could preface every imaginary list with a typename, and write functions that checked that the elements of these lists were the types they were supposed to be. This means that the types of all the elements of a large structure are checked often. Doubleplusungood.) Are these two strong and weak typing? Also, some languages do type-checking at compile-time, and some at run-time. Some (ML and others) typecheck at compile-time _but_ it does all the work itself. Run-time typecheckers maintain the type of a value with the value itself, and on each operation checks that the operation is an appropriate thing to do. Are these two static and dynamic typechecking? Thanks, and sorry for asking here but many people have said "X is the wrong word for Y" that I felt that the only people who could tell me what the words mean in the debate were the participants. ____ \/ o\ Paul Crowley aipdc@uk.ac.ed.castle \ / /\__/ Part straight. Part gay. All queer. \/
cs450a03@uc780.umd.edu (03/13/91)
The Question, as I understand it, is: Is it a "good thing" or a "bad thing" to require programmers to specify what types of information can be associated with a variable name. Sample argument for: Static typing catches errors. Sample argument against: Static typing encourages errors. Other argument against: Static typing slows development. Other argument for: Static typing allows faster code. Is that the gist of this, so far? Do I need to add that people favoring dynamic typing also favor dynamic testing? Or that existing popular systems (X-windows and emacs come to mind) are not paragons of efficiency? My own, biased, observation is that keeping information and control paths simple goes a long, long way towards catching errors. Static typing, as a sort of globalish way of specifying data by side effect often does little towards simplifying either control or data flow. The problem being, essentially, that data applicable to function F may or may not be applicable for function G. (Or, similarly, datum X may be used to qualify datum Y, but not datum Z). For example, consider a function which finds the square of a number. Nice, simple (almost trivial) function. Yet on most computers, you can give it arguments which do not have representable squares. If you were to solve this problem with static typing, I suppose you could invent some special numeric type of limited magnitude, and say that only numbers of that type could be passed to this function. Does anyone detect a note of insanity here? Just how are you supposed to verify that the result of some arbitrary computation, each stage of which has its own domain (and its own range) will generate results which are in the domain of the next relation down the line? You might as well say "Lets find the solution to all problems, rather than this one problem, because then we'll be more correct." Yeah, right. May I humbly suggest, if you really think this (static typing) is a useful approach, that you spend a little time reviewing completeness and consistency? (Ask your favorite mathematician) Who knows, maybe you'll be able to solve the knapsack problem. Finally, to Mr. Grier, who posed the rhetorical problem about trusting software in critical systems which might have latent bugs: would you really trust such software if it had never been tested? Would it make you feel safer if each type of data required seperate chunks of code, with the associated tests and branches and variant storage mechanisms? This is slightly off the topic of data polymorphism, but I'd like to point out at work it is considered good form to write code that does not branch or loop (or use recursion), as much as possible. Remember, control flow (such as branching) is just another way of representing information. With the proper primitives, you can do an incredible amount of decision making with zero branching (sort of "super-structured"). Raul Rockwell
gudeman@cs.arizona.edu (David Gudeman) (03/13/91)
In article <YR+9VUB@xds13.ferranti.com> Peter da Silva writes: ]In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]> Now people have actually come to believe, against all ]> evidence, that static typing is important for program reliability. ] ]No, they believe that dynamic typing imposes a certain unavoidable runtime ]overhead, and aren't willing to pay that cost. You haven't been following the thread. Only one person of all that replied has mentioned the overhead. Everyone else is worried that types errors aren't detected by the compiler. BTW, I mentioned the high overhead in my first posting on the subject. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
grier@marx.enet.dec.com (03/13/91)
In article <YR+9VUB@xds13.ferranti.com>, peter@ficc.ferranti.com (Peter da Silva) writes: |> In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David |> Gudeman) writes: |> > Now people have actually come to believe, against all |> > evidence, that static typing is important for program reliability. |> |> No, they believe that dynamic typing imposes a certain unavoidable |> runtime |> overhead, and aren't willing to pay that cost. As computers get |> faster this |> becomes more of a moot point outside the frantic world of embedded |> controllers |> and videogames. |> Actually, reliability is exactly my argument. I think that in most cases, the static typing of an expression can be inferred, and thus the best instruction sequence be selected. (Going out on a limb here, but I've never been known to be timid in making statements or speculations.) My argument is for reliability in engineering of large software systems, where a type or operator change might produce an error embedded somewhere which cannot be determined statically (remember, there still *has* to be a case where the type inference logic can't determine the type of an expression, and thus has to assume the worst,) and might not be found for quite some time. If this is your favorite game, this might be annoying but not truly harmful. If this is a nuclear power plant or air traffic controller station, well, let's just hope it doesn't happen. I also claim agnostance(is that a real word?) on the utility of dynamic typing. Other than BASIC, I can't say I've ever used it to build any real software, and I'd be a lot happier to have a hard and fast notion of the type to which a reference/variable refers at any given point in the program text than to freely toss about dynamically typed variables. But I'm poisoned from learning FORTRAN and BASIC at a frightfully early age and I'm probably ruined for life in that respect. :-) -mjg ------------------------------------------------------------------------ I'm saying this, not Digital. Don't hold them responsibile for it! Michael J. Grier Digital Equipment Corporation (508) 496-8417 grier@leno.dec.com Stow, Mass, USA Mailstop OGO1-1/R6
gudeman@cs.arizona.edu (David Gudeman) (03/13/91)
In article <25381:Mar1221:07:3891@kramden.acf.nyu.edu> Dan Bernstein writes:
]
]Compiler writers generally find it convenient to catch type errors.
In a language with dynamic typing, everything from the parser to the
code generator tends to be much simpler. Only the runtime system is
more complex (and not much more complex if you don't add all the extra
features that dynamic typing opens up).
]Programmers generally find it convenient to know the type of a variable
Programmers do know the types of variables with dynamic typing, they
just don't have to tell the computer.
]and to have types always checked at compile time (rather than as an
]optimization that a few compilers might provide). Who are you to argue
]with taste?
I'm someone who has experience with both methods, arguing mostly (I
suspect) with people who don't.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
multics@acm.rpi.edu (Richard Shetron) (03/13/91)
My personal choice would be a language that supported both static and dynamic type variables. There have been times when one or two dynamic typed variables would have been very useful. I also like static typing as I've had good luck with compilers finding type mismatches that were errors or possible sources of problems. I'm much more experienced with static typed languages then dynamic, but I would much prefer a language that supported both. -- A good bureaucracy is the best tool of oppression ever invented. Richard Shetron USERFXLG@rpi.mts.edu multics@mts.rpi.edu
Chris.Holt@newcastle.ac.uk (Chris Holt) (03/13/91)
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: >In article <609@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >> Required declarations are not in languages because they >> are a good idea, >I have to disagree. Declarations help catch typos, if nothing else. So in prototype-style code, leave declarations out, and in production code put them in. Would you want to have to declare input types when using a pocket calculator? On the other hand, would you want a theorem prover to have to work from scratch, without any hints as to the legitimate domains of variables? ----------------------------------------------------------------------------- Chris.Holt@newcastle.ac.uk Computing Lab, U of Newcastle upon Tyne, UK ----------------------------------------------------------------------------- "A peace I hope with honour." - Disraeli 1878
sakkinen@tukki.jyu.fi (Markku Sakkinen) (03/13/91)
In article <613@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <YR+9VUB@xds13.ferranti.com> Peter da Silva writes: >]In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >]> Now people have actually come to believe, against all >]> evidence, that static typing is important for program reliability. >] >]No, they believe that dynamic typing imposes a certain unavoidable runtime >]overhead, and aren't willing to pay that cost. > >You haven't been following the thread. Only one person of all that >replied has mentioned the overhead. Everyone else is worried that >types errors aren't detected by the compiler. BTW, I mentioned the >high overhead in my first posting on the subject. Your original statement would seem to refer to people (programmers, managers who decide upon programming languages, etc.) in general, not only us Usenet freaks who are taking part in this illustrious thread of discussion. Surely at least Peter da Silva meant it that way, and I think very often the reason he suggested is the most important one. Just think about "typical" C hackers. Although C has some degree of static typing, they could not care less for "program reliability". On the other hand, they easily get mad about any byte of runtime overhead that might want to creep into their objects. The same horror of overhead seems to be common even among C++ programmers. Markku Sakkinen Department of Computer Science and Information Systems University of Jyvaskyla (a's with umlauts) PL 35 SF-40351 Jyvaskyla (umlauts again) Finland SAKKINEN@FINJYU.bitnet (alternative network address)
grier@marx.enet.dec.com (03/14/91)
In article <13MAR91.00251986@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: |> My own, biased, observation is that keeping information and control |> May I humbly suggest, if you really think this (static typing) is a |> useful approach, that you spend a little time reviewing completeness |> and consistency? (Ask your favorite mathematician) Who knows, maybe |> you'll be able to solve the knapsack problem. Well, my favorite mathematician is about a hundred miles away right now, but I studied under him for a few years, so I might speak up here. In mathematics, symbols bear more relation to unbound variables in lambda calculus (well, they bear a LOT of relation :-) In mathematics, it makes NO sense to talk about applying a function or operation to a symbol unless the symbol is known to be in the domain of the operation/function. I.e. writing something like "for all x, exp(x) is greater than zero" is nonsense. exp's domain is commonly the reals, and may be extended to bigger domains like the complexes, but that doesn't mean it applies to the empty set, or the tree outside my house's door. The correct way to write that would be, "for all x, if x is a real number, exp(x) is greater than zero." That's a type-case. I fully expect to make complete knowledge of the type of a symbol when I apply an operation to it, and any proof where applying an operation which might not be valid to a value is incorrect. (And programs are proofs of algorithms.) Now, that's the hard stance. Following this philosophy, if x is in the set of integers, x is also NOT in the set of reals (a nice copy of it and the rest of the integers are embedded there.) So, it's not uncommon to sleaze through your typing when there is a clear an unambiguous conversion implied. |> |> |> Finally, to Mr. Grier, who posed the rhetorical problem about trusting |> software in critical systems which might have latent bugs: would you |> really trust such software if it had never been tested? Would it make |> you feel safer if each type of data required seperate chunks of code, |> with the associated tests and branches and variant storage mechanisms? Yes and no. No first. "No", because that's why I think that subtyping and inheritance is so wonderful. If there's an obvious way to specialize an operation to a subtype, it *is* clearer and still absolutely statically correct to apply the supertype's operation to a value of the subtype. "Yes", because otherwise you end up putting operations up artifically high in the tree. The one which comes to mind and really burns me is when there's a "Shape" type, and because people want to have values of type "Shape" and want to apply the "diameter()" operation to them. Shapes don't all have diameters! At least if they do, it's not with the implicit meaning implied by the C++ folks who want to define a diameter() virtual member function for Shape. When the operations which can be performed by subtypes of a given variable's type differ from the operations valid for the type, that's where "Yes" comes into play. In David Gudeman's example, I most certainly don't want some "read()" operation which can return ANY type of value to allow my code to blindly attempt to apply the "+" or "Log()" operator to it. My code should have some reason to believe that "Log()" makes sense when applied to the value. Statically checked, before allowing me or anyone else to run it. My motivation is that I believe that compilers should try to do their best to ensure correctness of a program implementation before allowing it to be executed. If you don't believe in that, well, we differ and that's life. I just wouldn't do my banking or trust my life to software which relies on extensive testing rather than some level of ensurred correctness. (I don't claim that compilers will ever be able to prove correctness of the semantics of an algorithm, but I'd like them to at least ensure the correctness of the proof/implementation.) In addition, it's a little late now, but in my original posting, I conceded that this sort of feature would be appropriate for research and/or prototyping. Type checking is something like a spelling checker. If you're in a burst of creativity, it's a pain in the ass, so you turn it off - the flow of information and ideas is much more important than the static correctness of the spellings. However, for producing real reports or papers or books, you want to ensure not only that the ideas are generally correct, but that the "i"s are dotted and the "t"s are crossed. That's what strong static typing is. Heck, there's an even more direct parallel. Syntax checking! Next thing, why don't we have languages where they "try" to interpret commands and do what their best guess to the requested operations are! I don't WANT a smart computer, I don't WANT a computer which can misinterpret ambiguous commands, I don't WANT a computer which can forget. (machine checks/hardware failures don't count.) I want a computer which does EXACTLY what I tell it, really fast. If what I tell it to do doesn't or might not make sense, I want it to let me know so I can clear up any ambiguity as early as possible, rather than letting it find it out later (rush hour at a busy airport, suddenly over 256 planes are in the airspace, and some program writes past the end of an array, blasting away the stack, crashing the ATC computer,) or make some guess (it's fiction, but my fav. example is from a book by <forget first name> Hogan, _The Two Faces of Tomorrow_, where a semi-intelligent computer misinterprets an ambiguously worded command and ends up demolishing things...) (I also want to be able to build up a large library of things that I've already told the computer how to do and now either want to refine or use again. I also want it to tell me if I'm using something incorrectly, and/or if I change an existing component if it breaks other existing components which I may or may not know about.) This is tiring. If you don't agree with me, that's OK, but I hope you stay in the research world rather than producing software which people pay for and expect to work reliably. But if you're going to claim that this approach of dynamic typing increases {productivity, quality, performance, correctness, robustness}, I'll continue to differ with you. |> |> Raul Rockwell |> ------------------------------------------------------------------------ I'm saying this, not Digital. Don't hold them responsibile for it! Michael J. Grier Digital Equipment Corporation (508) 496-8417 grier@leno.dec.com Littleton, Mass, USA Mailstop OGO1-1/R6
pcg@test.aber.ac.uk (Piercarlo Antonio Grandi) (03/14/91)
On 13 Mar 91 01:21:07 GMT, gudeman@cs.arizona.edu (David Gudeman) said:
gudeman> In article <25381:Mar1221:07:3891@kramden.acf.nyu.edu> Dan
gudeman> Bernstein writes:
brnstnd> Compiler writers generally find it convenient to catch type
brnstnd> errors.
Yes, and this is simply part of the general principle that symbolic
reduction should go as far as possible during compile time. Everything
that is resolvable at compile time, i.e. that does not depend on
variables that will be bound only at runtime, or during further
compilation, should be resolved.
Another principle is that the programmer should have the ability to
decorate the source code with statements of his assumptions, to be
checked by the compiler or runtime system as appropriate.
Both principles above are embodied in something like Common Lisp's
'proclaim', on in the various type inference and explicit declaration
rules of many functional languages.
These two things mean that indeed static type checking should go as far
as possible, which is what you say, but not that there should be no
dynamic typing.
gudeman> In a language with dynamic typing, everything from the parser
gudeman> to the code generator tends to be much simpler. Only the
gudeman> runtime system is more complex (and not much more complex if
gudeman> you don't add all the extra features that dynamic typing opens
gudeman> up).
No, because the compiler will still have to do as much static checking
as it is possible, either by symbolic reduction or by checking
programmer's assumptions. I believe that compiler or runtime complexity
is not an issue, because one must have *both* static and dynamic
checking. Limiting onself only to one or the other seems constricting
(just static checking), as dynamic checking has then to be simulated or
many applications classes avoided, or hazardous (just dynamic checking),
as checks that do not depend on the particular paths of program
execution are not performed.
--
Piercarlo Grandi | ARPA: pcg%uk.ac.aber@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth | UUCP: ...!mcsun!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@aber.ac.uk
gudeman@cs.arizona.edu (David Gudeman) (03/14/91)
In article <9106@castle.ed.ac.uk> Paul Crowley writes:
--[description of a Logo's lack of type extension]--
]... Prolog behaves this way too.
In prolog you can create a new type by making it a set of terms with
the same name/arity. That makes it pretty much the same thing as a C
struct.
]What are the words for these two?
I'd call Logo "a language without type extension facilities". :-).
]... This means that the types of all the elements
]of a large structure are checked often. Doubleplusungood.)
That is what you have to do in general in dynamically typed languages,
and is what people are talking about when they say that dynamic typing
has a high overhead. It is also the main reason why I want both
dynamic and static typing -- I want dynamic typing to speed code
developement and upgrades, and I want static typing to eliminate the
overhead for bottlenecks in the code.
]Are these two strong and weak typing?
Strong and weak typing are relative terms. They refer to how many
areas the static type checking can break down. For example in K&R C,
you can pass an int to a function that expects a double, and you get
undefined behavior that tends to be hard to track down. In more
strongly typed languages, the types of values are checked across
procedure calls. There are hordes of niggling little details to
consider when trying to increase the "strength" of static typing.
Often "strong typing" is intended to be an absolute term, only
refering to languages with no known type checking loopholes.
]Also, some languages do type-checking at compile-time, and some at
]run-time. Some (ML and others) typecheck at compile-time _but_ it does
]all the work itself.
I thought you had to declare the types of functions in ML...
]Are these two static and dynamic typechecking?
Yes.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
aipdc@castle.ed.ac.uk (Paul Crowley) (03/14/91)
In article <618@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <9106@castle.ed.ac.uk> Paul Crowley writes: >--[description of a Logo's lack of type extension]-- >]... Prolog behaves this way too. > >In prolog you can create a new type by making it a set of terms with >the same name/arity. That makes it pretty much the same thing as a C >struct. How is a language in which you can say [imaginary,5,3] different from one in which you can say imaginary(5,3)? My problem with both is the way they're both perfectly happy with imaginary(this_doesnt,make_sense). You could solve it with a dynamically typed OO language, I suppose. ____ \/ o\ Paul Crowley aipdc@uk.ac.ed.castle \ / /\__/ Part straight. Part gay. All queer. \/
schwartz@groucho.cs.psu.edu (Scott Schwartz) (03/14/91)
gudeman@cs.arizona.edu (David Gudeman) writes: | I thought you had to declare the types of functions in ML... % sml Standard ML of New Jersey, Version 0.66, 15 September 1990 - fun foo x = x + 1; val foo = fn : int -> int - fun bar x = x + 1.0; val bar = fn : real -> real
kers@hplb.hpl.hp.com (Chris Dollin) (03/14/91)
[Abbreviations: STL - statically typed language; DTL - dyamically T'd L.]
Dan Bernstein writes:
Compiler writers generally find it convenient to catch type errors.
Er ... only if (begging the question) they're dealing with STL's. [Even then,
it's not *convenient*, it's *necessary* - otherwise the compiler would be
wrong.]
Programmers generally find it convenient to know the type of a variable
and to have types always checked at compile time (rather than as an
optimization that a few compilers might provide). Who are you to argue
with taste?
Alternative A: I'm not a programmer; I don't find it "convenient to know the
type of a variable and to have types always checked at compile time".
I am reluctant to endorse this alternative because it seems to leave the
majority of my life's income (and the behaviour of a non-empty set of
computers) unexplained.
Alternative B: It is not true that "programmers generally ...". Programmers in
STL's have no choice. (Programmers in DTL's sometimes have the opposite
choice.)
Since I know several individuals who would satisfy the usual crieria for being
programmers who don't "find it convenient ...", alternative B seems plausible.
[Other alternatives are imaginable.]
Observation: this is all a bit silly anyway. [Didn't we have the B&D debate a
few months ago?] The poles of argument seem to be:
* one ought to be able to write anything that makes sense in its context
* it's much nicer to catch errors at compile-time than run-time, if possible
Since I suspect that no-one would seriously disagree with these two poles, we
can concentrate on
* how much expressiveness are you prepared to give up for the cost of being
able to check correctness (be it type-correctness or whatever) at compile-time?
Bear in mind that in (say) Pascal, checking that an expression E has type T may
require a run-time check (suppose T is 1..10 and E is i+1, i:T), so a
presumptive STL may require run-time checks.
Given the choice between (say) C and Pop (or Lisp) I'd pick Pop (or, if I had
to, Lisp). As David G says, type errors turn out to be just not that frequent,
because you just don't write ill-typed code very often, and when you do, it
usually surfaces quickly. [The argument that it might not, and remain a logic
bomb lurking in your code ready to pounce on an unsuspecting user, is moot;
"real" logic errors can so lurk, but because we know they cannot be cheaply
detected at compile-time, no-one is upset that thay are not so detected;
instead they deploy design, and code reviews, and testing; in short, Software
Engineering.]
[Of couse, for Pepper (the Pop-like language I'm building at the moment) I'd
like to design a static type system which will spot likely type errors at
compile-time, but not prevent you from overriding it; since Pepper programs
already work quite happily with no static typechecks, I'm in no hurry to build
a grotty little pedant rather than a pleasant cardboard cutout.]
--
Regards, Kers. | "You're better off not dreaming of the things to come;
Caravan: | Dreams are always ending far too soon."
gudeman@cs.arizona.edu (David Gudeman) (03/14/91)
In article <1991Mar13.163629.12630@engage.enet.dec.com> grier@marx.enet.dec.com writes:
]
] In mathematics, it makes NO sense to talk about applying a function
]or operation to a symbol unless the symbol is known to be in the
]domain of the operation/function. I.e. writing something like "for all
]x, exp(x) is greater than zero" is nonsense.
Nonsense. Anybody with a clue immediately understands that x is
restricted to values in the domain of exp.
]... In David Gudeman's example, I most
]certainly don't want some "read()" operation which can return ANY
]type of value to allow my code to blindly attempt to apply the "+" or
]"Log()" operator to it.
In the first place, the read() only produced ints and floats, and the
return value was used in a context where either was a legal value. In
the second place, if the read() function were defined to return other
sorts of values and you wrote "x + read()" the problem would be in the
program, not in the definition of "read()".
]My code should have some reason to believe
]that "Log()" makes sense when applied to the value. Statically checked,
]before allowing me or anyone else to run it.
Then you are going to need types "positive int" and "positive float".
Do you also want type security for division? Then you are going to
need "non-0 int", "non-0 float", as well as "positive non-0 int", etc.
I count 8 numeric types needed just to get type security for two
common operations. The point is that static type checking involves an
essentially arbitrary set of restrictions, and there is no evidence
that it adds to program security at all.
I will concede that optional type declarations might help program
security in the same way that optional assert clauses do. (I don't
object to having type declarations in a language, I object to
_required_ type declarations.) However, for many variables there is
no need to declare the type since it is obvious from the var's use,
and programmers should be smart enough to know the difference.
Whatever makes a language designer think --at language design time--
that he knows more about the requirements of a program than the
programmer will know --at program design time?
]... I just wouldn't do my banking or trust my life to software which
]relies on extensive testing rather than some level of ensurred correctness.
You must be joking. Static type checking doesn't give any reasonable
level of assurance at all -- it is never the case that simply because
program compiles without errors, there is reason to believe that it
has some level of reliability. Testing is the _only_ known way to
give any assurance at all. And a given amount of testing generally
provides more assurance for a language with dynamic typing than it
would for a language with static typing. (Because programs in
dynamically typed languages are usually much smaller and have fewer
paths to test.)
] Heck, there's an even more direct parallel. Syntax checking! Next thing,
]why don't we have languages where they "try" to interpret commands and
]do what their best guess to the requested operations are!
]
] I don't WANT a smart computer, I don't WANT a computer which can
]misinterpret ambiguous commands, I don't WANT a computer which
]can forget...
There is no parallel there at all. Dynamically typed languages in no
sense try to interpret ambiguous commands or "guess" what is wanted.
The typing rules are just as unambiguous in a dynamically typed
language as in a statically typed language. But in the dynamically
typed language the rules are simpler, more intuitive, and easier to
use. (Depending on the language of course -- I wouldn't argue that
Smalltalk's rules are any of the above...)
]... and some program writes past the end of an array, blasting
]away the stack, crashing the ATC computer,)
You are confusing weak typing with dynamic typing. I don't know of
any dynamically typed language that lets you write past the end of an
array (unless it first expands the array).
] This is tiring. If you don't agree with me, that's OK, but I hope you
]stay in the research world rather than producing software which people
]pay for and expect to work reliably.
I'm sure that the thousands of people currently using and relying on
software written in dynamically typed languages are touched by your
concern. We aren't worried about it though, we'll just go on using
easily modifiable software that was written in half the time with
twice the functionality, with no loss of reliabilty. (Although we
_do_ have to buy faster machines or put up with slower response.)
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/14/91)
In article <1991Mar13.143805.28242@tukki.jyu.fi> Markku Sakkinen writes: ]In article <613@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]>]In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]>]> Now people have actually come to believe, against all ]>]> evidence, that static typing is important for program reliability. ] ]Your original statement would seem to refer to people Not if you were following the thread. And frankly, not even if you read the article carefully. I was not speaking in opposition to static type checking, I was specifically talking about this religious belief that static type checking is necessary for program reliability. I was the first person in this discussion to say that dynamic typing is less efficient, and that efficiency was a good reason to use static typing sometimes. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
anw@maths.nott.ac.uk (Dr A. N. Walker) (03/14/91)
In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: [Disclaimer: I usually find DG's contributions to be thoughtful and accurate. Exceptionally, I think this one was ill-conceived, and dangerously close to a polemic. This may have coloured my reaction to it. -- ANW] > Mathematical notation is generally >closer to dynamically typed languages than to statically typed >languages. & it was invented and had evolved long before the whole modern problem of trying to describe problems and algorithms in a precise way for computer consumption. Such examples as we have of attempts at precision in maths [eg, by Russell] are not very convincing. We write sloppy maths because "we all know what we mean" -- I don't think that's a very good model for computer languages. > Static typing >originated, as near as I can determine, with low-level languages like >Fortran an Algol that were little more than glorified assemblers. [I assume that Algol 60, rather than older or modern versions, is meant.] This statement is just historically ignorant. Algol 60 was *specifically* designed to describe algorithms, independently of the computer. Static typing was certainly not put in to make code generation easier; indeed, it was widely thought that Algol 60 would never be used in real compilers. Fortran did not, for the most part, have declarations at all; and many people argued bitterly, using just the same "busy work" arguments, against having them in Algol. The view that you should say what variables you want to use, and what you want to use them for, prevailed (for good reason, in my opinion). > For example if the length of an array is not included >in the definition of the type, you can do arbitrary things to memory >by setting out-of-bounds values in the array. Semantically we say >that the behavior of the program becomes undefined due to a type >error. Well this is a matter of semantics [:-)]. The C fragment "int a[10]; a[23] = 17;" might, in many implementations, do something arbitrary to memory, but in my opinion it contains an error. Making the behaviour undefined is [perhaps wrong-headedly] a convenience for the compiler writer. Would C become more strongly typed if the behaviour became defined in some way? >With static typing you need a great deal of information at compile >time to be able to guarantee strong typing. This has two >consequences: (1) you have to limit the forms of expressions to some >set for which you know a type-checking decision procedure, and (2) you >have to acquire type information somewhere. Ie, (1) you have to know what your expression is intended to do, and (2) you have to use variables in a disciplined way. I don't find these "consequences" either irksome or undesirable. When I write programs in dynamically typed languages, I try hard to follow the same precepts. > The computer is supposed to to busy work like checking >type consistency, the programmer should no more be burdened with this >than he should have to calculate constants. Type *consistency* is indeed the compiler's job, but I don't find it unreasonable that I should document my identifiers. > How would you like it if >you could not rely on constant folding, so you had to calculate the >values of all your constants? [Irrelevant aside: you *can not* rely on constant folding. The fragment "i := 2; j := 3; if i/j != 2/3 then ..." will indeed "fail" on many [arguably broken] systems, which may matter to the Numerical Analyst.] There is a place for dynamic typing (I enjoy writing shell scripts!), and a case for rapid prototyping, but there is also a case for traditional declarations; there is no need for either "camp" to knock the other. -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
grier@marx.enet.dec.com (03/15/91)
In article <626@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David Gudeman) writes: |> In article <1991Mar13.163629.12630@engage.enet.dec.com> |> grier@marx.enet.dec.com writes: |> ] |> ] In mathematics, it makes NO sense to talk about applying a |> function |> ]or operation to a symbol unless the symbol is known to be in the |> ]domain of the operation/function. I.e. writing something like "for |> all |> ]x, exp(x) is greater than zero" is nonsense. |> |> Nonsense. Anybody with a clue immediately understands that x is |> restricted to values in the domain of exp. |> Wrong. If you consider a proof to have some static notion of correctness (which is exactly what they are,) this kind of error is absolutely wong in a formal exhibition of the proof. True, when we're talking and scribbling on the board working through examples, we'd use the "usual" meanings and not necessarily be exact, but if you're submitting the proof for formal inspection, in a journal or book or on an exam, IT IS WRONG. But then, I believe this is also the root of my belief that static typing of variables, included with inheritance for reuse and genericity and some sort of a "type_case" construct to permit more specialized operations in a controlled fashion, is correct. (i.e. it's implicit that if I write "5+6 = 11" on the board, I'm referring to the addition operator for the integers or possibly naturals. If you think I'm arguing for specially writing code to deal with all of the possibly addition definitions, you're missing the part of my argument about subtyping.) |> ]... In David Gudeman's example, I most |> ]certainly don't want some "read()" operation which can return ANY |> ]type of value to allow my code to blindly attempt to apply the "+" or |> ]"Log()" operator to it. |> |> In the first place, the read() only produced ints and floats, and the |> return value was used in a context where either was a legal value. In |> the second place, if the read() function were defined to return other |> sorts of values and you wrote "x + read()" the problem would be in the |> program, not in the definition of "read()". Where is this knowledge known? Your arguments so far have been to not explicitly state the type of value accepted by or returned by an operator. And it still doesn't help when "Read()" is extended in the future to return strings, cats and dogs. |> |> ]My code should have some reason to believe |> ]that "Log()" makes sense when applied to the value. Statically checked, |> ]before allowing me or anyone else to run it. |> |> Then you are going to need types "positive int" and "positive float". |> Do you also want type security for division? Then you are going to |> need "non-0 int", "non-0 float", as well as "positive non-0 int", etc. |> I count 8 numeric types needed just to get type security for two |> common operations. The point is that static type checking involves an |> essentially arbitrary set of restrictions, and there is no evidence |> that it adds to program security at all. That's silly. I'm arguing this in the light of subtyping, so you could have some sort of abstract subtype called "Algebraic" or "Number" where it has the "usual" operations defined. ReadNumber() would return a "Number" to permit at least type-safety. This is a case where the compiler would not have a choice but defer the binding of the operation invocation to actual code/methods until run-time, but it's still working in a statically typed type-safe environment. |> |> I will concede that optional type declarations might help program |> security in the same way that optional assert clauses do. (I don't |> object to having type declarations in a language, I object to |> _required_ type declarations.) However, for many variables there is |> no need to declare the type since it is obvious from the var's use, |> and programmers should be smart enough to know the difference. |> Whatever makes a language designer think --at language design time-- |> that he knows more about the requirements of a program than the |> programmer will know --at program design time? |> She doesn't. That's why I argue for subtyping/inheritance/genericity/etc. The language is the framework and syntax to work in. It should be minimal and formal. There's no one language for mathematics, but if you DO pick one, it's very formal and minimal (usually it just involves notions of n-ary operators, groupings via parenthesis - not necessary if you use prefix notation - and quantifiers. But look what you can do once you've build up your base of operations and theorems!) |> ]... I just wouldn't do my banking or trust my life to software which |> ]relies on extensive testing rather than some level of ensurred correctness. |> |> You must be joking. Static type checking doesn't give any reasonable |> level of assurance at all -- it is never the case that simply because |> program compiles without errors, there is reason to believe that it |> has some level of reliability. Testing is the _only_ known way to |> give any assurance at all. And a given amount of testing generally |> provides more assurance for a language with dynamic typing than it |> would for a language with static typing. (Because programs in |> dynamically typed languages are usually much smaller and have fewer |> paths to test.) Actually, formal proof is the only known way to ensure any measure of static correctness. Testing is like the old joke about "all odd numbers are prime"... (let's see... 3, 5, 7, wow, yeah, I guess they are!) Exhaustive testing is also possible, but the claim nowadays is that software systems are the most complex things mankind has ever built. Now, I know that simulations of electronic circuits usually run for hours/months trying to exhaustively test strange conditions. And they *still* find bugs years later due to some strange signal path. You're going to tell me that some poking around by either the developers or users is going to exhaustively test a software system? Formal proof is rather unwieldy in my opinion in software right now, but I believe it's because we don't have a good enough set of theorems to work with. (The CS proofs I read tend to either be extremely vague, or end up doing the equivalent of re-proving that 1+1=2 over and over again. I argue that code is a proof of an algorithm, and the compiler should do as much as it can to check it before allowing it to be executed. Certain semantics are IMO beyond the realm of computers to verify, but correctness of structure and syntax are certainly possible.) We're well beyond the topic now though... back to dynamic typing. |> |> ] Heck, there's an even more direct parallel. Syntax checking! |> Next thing, |> ]why don't we have languages where they "try" to interpret commands |> and |> ]do what their best guess to the requested operations are! |> ] |> ] I don't WANT a smart computer, I don't WANT a computer which can |> ]misinterpret ambiguous commands, I don't WANT a computer which |> ]can forget... |> |> There is no parallel there at all. Dynamically typed languages in |> no |> sense try to interpret ambiguous commands or "guess" what is wanted. |> The typing rules are just as unambiguous in a dynamically typed |> language as in a statically typed language. But in the dynamically |> typed language the rules are simpler, more intuitive, and easier to |> use. (Depending on the language of course -- I wouldn't argue that |> Smalltalk's rules are any of the above...) |> Once you've built up a large type library, the chances that any one person has a detailed gestalt of the whole thing is very unlikely. Therefore, unless you explicitly restrict the type of a value you receive, you can only assume that it's any type. Therefore the complex operation application rules (well, they're probably not complex, but when you have a library of hundreds or thousands of types you start to hit a level of complexity where it *looks* almost like guessing,) aren't totally predictable and you can get an unexpected operation invoked. That's clearly a loose argument. Again, one more time, with feeling, my concern is with large software systems. If you're toying around with some new means to perform parallel decomposition of a relational join, your domain is probably small enough that you could maintain the simulation correctly yourself with a dynamically typed system. This is great for prototyping and research. Let the ideas flow! Real-life systems get quite large and no one person has the complete and total understanding of the whole system that you might in a limited domain. This is my argument. I'm sorry, David, if I've taken away from the pure discussion of the topic and it's an interesting one even outside of the engineering aspects, but I'm responding largely to the claims that some testing by (a) people who know the system or (b) people who are likely to only push the buttons that the (a) folks told them to are going to ensure anything. Having something of a mathematical background, I believe that the True Path to Software Engineering and Correctness is by building up layers and levels of provably correct software. (If I'm proving some sort of extension theorem in topology, I don't have to continually prove basic identities of fields.) I'm also a fan of Eiffel, which allows for building up of these layers, refining them as you go along. (It has its problems, but more than any other language I know of today, I think it's on the right track for enabling building of large extensible systems.) |> ]... and some program writes past the end of an array, blasting |> ]away the stack, crashing the ATC computer,) |> |> You are confusing weak typing with dynamic typing. I don't know of |> any dynamically typed language that lets you write past the end of an |> array (unless it first expands the array). |> You're right. I was in unnaturally early yesterday and not everything came out as it would have after a good cup of coffee. :-) |> ] This is tiring. If you don't agree with me, that's OK, but I hope you |> ]stay in the research world rather than producing software which people |> ]pay for and expect to work reliably. |> |> I'm sure that the thousands of people currently using and relying on |> software written in dynamically typed languages are touched by your |> concern. We aren't worried about it though, we'll just go on using |> easily modifiable software that was written in half the time with |> twice the functionality, with no loss of reliabilty. (Although we |> _do_ have to buy faster machines or put up with slower response.) Yup, and it's going to cost when we have to maintain those systems for the next 5-20 years. Or are we going to just re-develop them all over again when we need to extend them? There go all your claims of productivity and efficiency for the programmer. Remember that most of the cost of a software system is in the long term maintenance and evolution - not in the initial development. |> -- |> David Gudeman |> gudeman@cs.arizona.edu |> noao!arizona!gudeman |> ------------------------------------------------------------------------ I'm saying this, not Digital. Don't hold them responsibile for it! Michael J. Grier Digital Equipment Corporation (508) 496-8417 grier@leno.dec.com Littleton, Mass, USA Mailstop OGO1-1/R6
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/15/91)
In article <1991Mar13.124811.1380@newcastle.ac.uk> Chris.Holt@newcastle.ac.uk (Chris Holt) writes: > So in prototype-style code, leave declarations out, and in production > code put them in. Well, I usually start writing a program by seeing how the data will be organized. Once I know this, I might as well tell the computer about it. (This is an observation about my programming style, nothing more; I never decided that---what do they call it now?---``data structure design'' was the Right Way to Code, and I can't argue that it's better or worse than any other style.) What would I gain from pushing off explicit declarations until the last moment? Are you saying that it's not important to catch typos and type errors during debugging? ---Dan
cs450a03@uc780.umd.edu (03/15/91)
Grier: > In mathematics, it makes NO sense to talk about applying a function >or operation to a symbol unless the symbol is known to be in the >domain of the operation/function. I.e. writing something like "for all >x, exp(x) is greater than zero" is nonsense. exp's domain is commonly >the reals, and may be extended to bigger domains like the complexes, but >that doesn't mean it applies to the empty set, or the tree outside my >house's door. The correct way to write that would be, "for all x, if >x is a real number, exp(x) is greater than zero." Hmm... so what is the correct way of finding if x is a member of set y? What is the domain of a function which selects the first 5 from an ordered sequence? What is the domain of = (test-if-equal)? What is the domain of encapsulation (obtain-a-pointer-to)? What is the domain of search (look-up-in-table)? Why should I limit assignment (name-association) based on the domain of, for instance, addition? > ... > Now, that's the hard stance. Following this philosophy, if x >is in the set of integers, x is also NOT in the set of reals (a nice >copy of it and the rest of the integers are embedded there.) So, >it's not uncommon to sleaze through your typing when there is a clear >an unambiguous conversion implied. And which of those types is the domain of log() ? Can you think of a good reason to not have runtime type-checking for log? >|> Finally, to Mr. Grier, who posed the rhetorical problem about trusting >|> software in critical systems which might have latent bugs: would you >|> really trust such software if it had never been tested? Would it make >|> you feel safer if each type of data required seperate chunks of code, >|> with the associated tests and branches and variant storage mechanisms? > > Yes and no. No first. > > "No", because that's why I think that subtyping and inheritance is >so wonderful. If there's an obvious way to specialize an operation to a >subtype, it *is* clearer and still absolutely statically correct to apply the >supertype's operation to a value of the subtype. No this makes you feel safer? Or no it doesn't make you feel safer? > "Yes", because otherwise you end up putting operations up artifically >high in the tree. The one which comes to mind and really burns me is >when there's a "Shape" type, and because people want to have values of >type "Shape" and want to apply the "diameter()" operation to them. Shapes >don't all have diameters! At least if they do, it's not with the implicit >meaning implied by the C++ folks who want to define a diameter() >virtual member function for Shape. Sounds like a good reason to avoid testing to me. (Right folks?) > My motivation is that I believe that compilers should try to do their >best to ensure correctness of a program implementation before allowing >it to be executed. If you don't believe in that, well, we differ and >that's life. I just wouldn't do my banking or trust my life to software which >relies on extensive testing rather than some level of ensurred correctness. I have no objection to compilers doing their best. Nor do I claim that testing somehow negates the responsibility of the designer to ensure correctness. Nor do I claim that the compiler relieves the designer of the responsibility for correctness. On the other hand, I do claim that IF the language lets me, I can write programs which can be verified quite simply. The technique is very similar to induction. (Check boundary conditions, check for typical case). Sometimes you can make your testing into a proof of correctness (given basic assumptions about the correctness of the underlying machinery). Sometimes not. As an aside, you can test both your understanding of a concept, and its correctness by implementing it and seeing if it works. Proofs can have bugs in them too, you know. > (I don't claim that compilers will ever be able to prove correctness >of the semantics of an algorithm, but I'd like them to at least ensure the >correctness of the proof/implementation.) And how is a compiler supposed to prove that a program fulfills its purpose? At best, the compiler can prove that the program can be compiled. >... > I don't WANT a smart computer, I don't WANT a computer which can >misinterpret ambiguous commands, I don't WANT a computer which >can forget. (machine checks/hardware failures don't count.) I do not believe I was arguing for any of these features. > I want a computer which does EXACTLY what I tell it, really fast. If what >I tell it to do doesn't or might not make sense, I want it to let me know >so I can clear up any ambiguity as early as possible, rather than letting it >find it out later (rush hour at a busy airport, suddenly over 256 planes are >in the airspace, and some program writes past the end of an array, blasting >away the stack, crashing the ATC computer,) or make some guess ... A very good reason for runtime type checking. > This is tiring. If you don't agree with me, that's OK, but I hope you >stay in the research world rather than producing software which people >pay for and expect to work reliably. But if you're going to claim that >this approach of dynamic typing increases {productivity, quality, >performance, correctness, robustness}, I'll continue to differ with you. I must admit that following your line of thought (or attempting to-- I'm not sure I succeeded) is tiring for me as well. I should point out that I am not in the research world, and work very hard to see that what people pay for works well. And while I disclaim knowledge of what you think dynamic typing is, I do claim that type as a property of DATA as opposed to a property of a NAME does increase productivity, quality, correctness as well as robustness. In fact, I claim that assigning types to names, independent of the assigned values, is a akin to side-effect driven programming. I realize that it is necessary at a low level. I don't see it as a productivity boost. Raul Rockwell
grier@marx.enet.dec.com (03/15/91)
In article <14MAR91.22372006@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: |> |> Hmm... so what is the correct way of finding if x is a member of set |> y? What is the domain of a function which selects the first 5 from |> an ordered sequence? What is the domain of = (test-if-equal)? What |> is the domain of encapsulation (obtain-a-pointer-to)? What is the |> domain of search (look-up-in-table)? I don't see what point you're trying to make. If these aren't take a look at most any object-oriented system designed in the last decade or so with strong typing and parametrized types. Or even outside the OO world per se., in perhaps CLU. (In direct answer, picking a syntax, perhaps "contains(y, x)", "select_first_n(seq, 5)", "Object", huh? let's keep pointers out of this.., assuming that the Table type is parametrized ala "Table[KeyType, ValueType]", it's the type specified for KeyType when the particular Table value instance was created.) |> |> Why should I limit assignment (name-association) based on the domain |> of, for instance, addition? I don't know, why should you? I think you're trying to twist what I'm saying somehow but I don't see your point. Perhaps it's your terminology. I'll spell it out. If in some scope you have a variable, let's call it X, which is declared to be of type "Number" (presuming all the types with algebraic-type operations fall under there for the sake of argument,) the compiler should ensure that any value I attempt to bind X to is compatible with the type of X. (I.e. you can assign 5, pi, e, 27, 42, 0.00000001, maybe even 2+3i to X. But you couldn't assign "my car", "the red firetruck" or "The Soviet Union" to X. Insert appropriate type-checking rules for generics/parametrized types.) |> |> > ... |> > Now, that's the hard stance. Following this philosophy, if x |> >is in the set of integers, x is also NOT in the set of reals (a nice |> >copy of it and the rest of the integers are embedded there.) So, |> >it's not uncommon to sleaze through your typing when there is a clear |> >an unambiguous conversion implied. |> |> And which of those types is the domain of log() ? Can you think of a |> good reason to not have runtime type-checking for log? |> I can think of a better reason not to have runtime type checking for log: if I'm doing it quite a bit, the runtime costs are high. Wasn't that part of the whole original premise or why David Gudeman made his original postings? I believe that Ada's model here is correct. No correct program may depend on exceptions for its operation. (Implying that if you have a correct program, you can turn off all the nice bounds-testing and such and end up with a nice and *fast* program. Commercial Ada compilers from both Rational and Digital do this.) |> >|> Finally, to Mr. Grier, who posed the rhetorical problem about trusting |> >|> software in critical systems which might have latent bugs: would you |> >|> really trust such software if it had never been tested? Would it make |> >|> you feel safer if each type of data required seperate chunks of code, |> >|> with the associated tests and branches and variant storage mechanisms? |> > |> > Yes and no. No first. |> > |> > "No", because that's why I think that subtyping and inheritance is |> >so wonderful. If there's an obvious way to specialize an operation to a |> >subtype, it *is* clearer and still absolutely statically correct to |> apply the |> >supertype's operation to a value of the subtype. |> |> No this makes you feel safer? Or no it doesn't make you feel safer? Beings I discusses my thoughts on both counts, does it matter other than you wanting to pick nits? |> |> > "Yes", because otherwise you end up putting operations up artifically |> >high in the tree. The one which comes to mind and really burns me is |> >when there's a "Shape" type, and because people want to have values of |> >type "Shape" and want to apply the "diameter()" operation to them. Shapes |> >don't all have diameters! At least if they do, it's not with the implicit |> >meaning implied by the C++ folks who want to define a diameter() |> >virtual member function for Shape. |> |> Sounds like a good reason to avoid testing to me. (Right folks?) |> Testing what? What does that have to do with placing operations artificially high in an inheritance/subtyping tree? This is a design issue more than a testing one. I am most certainly not arguing against testing. However, if you haven't had an error reported, it doesn't mean it doesn't exist - it just means that either the tester hasn't found it or didn't feel like reporting it that day. |> |> I have no objection to compilers doing their best. Nor do I claim |> that testing somehow negates the responsibility of the designer to |> ensure correctness. Nor do I claim that the compiler relieves the |> designer of the responsibility for correctness. |> Ok, we're in total agreement here. I feel that there is/should be an important synergy between programmer and compiler here. (Good) compilers can find a great deal more optimizations than people can on silly things like removing loop invariants, etc. People can do much more important optimizations on newer and better spiffy algorithms. However, people shouldn't be wasting their time second-guessing the compiler. But I digress as usual... |> On the other hand, I do claim that IF the language lets me, I can |> write programs which can be verified quite simply. The technique is |> very similar to induction. (Check boundary conditions, check for |> typical case). Sometimes you can make your testing into a proof of |> correctness (given basic assumptions about the correctness of the |> underlying machinery). Sometimes not. |> Right, for a single program in a single instance. If you've been following what I've been saying, my argument has been around the long term maintenance of large systems. I could add, subtract and multiple and even take derivatives and come up with anti-derivatives long before I understood any of the theory. Loose induction and random sampling don't make for proofs. Simple programs can be verified simply. Complex ones are harder. This is one of several places where "programs as proofs" falls apart. My central argument is in long term management of large software projects. |> As an aside, you can test both your understanding of a concept, and |> its correctness by implementing it and seeing if it works. Proofs can |> have bugs in them too, you know. |> No arguments here! Again your comments make me believe that you don't understand my stance. Please re-read my postings if you have them available. |> > (I don't claim that compilers will ever be able to prove correctness |> >of the semantics of an algorithm, but I'd like them to at least ensure the |> >correctness of the proof/implementation.) |> |> And how is a compiler supposed to prove that a program fulfills its |> purpose? At best, the compiler can prove that the program can be |> compiled. |> Re-read my previous posting today. (well, yesterday at this point.) |> >... |> > I don't WANT a smart computer, I don't WANT a computer which can |> >misinterpret ambiguous commands, I don't WANT a computer which |> >can forget. (machine checks/hardware failures don't count.) |> |> I do not believe I was arguing for any of these features. (again) Read my posting earlier today. I claim that once a type hierarchy grows beyond the immediate and total understanding of one person at a time, and into a depth of complexity where the application of the algorithms for operation selection tend towards guessing/smartness. (I don't believe in AI per se, but I can imagine that applying even simple pattern matching rules against a large enough rule base could possibly maybe someday potentially doubtfully pass a turing test. This is exactly the situation when you have a large projects. Whether you're dealing with a decent modular language like Ada or Modula-2, or into the OO world with an Eiffel, Trellis or Modula-3, you need automated ensurance of type safety.) You're sitting on the bridge of the enterprise. "Sulu, press the red button". (Unbeknownst to you, Captain Kirk, a new Red self-destruct button was installed right next to the Warp Factor 1.0 button which used to be called red but might now be considered a dingy maroon. ka-bam-ooo... it's a shame, there was once a time when you knew that ship from stem to stern. If only Star Fleet had had the sense to make a regulation that only one Red button would be on the navigator's console, but nobody thought that such regulations and checks were necessary...) Once again, this is a problem I see in large systems evolving over time. My motivation isn't so much to prove correctness as to ensure that changes and growth don't invalidate other algorithms' implementations. My extending "Read()" to return baby names in addition to numbers broke David Gudeman's program which assumes they're numbers, unfortunately. I want the system to be able to recognize these problems and prevent them from being made into a running system. We all understand that even in a strongly typed language where type information isn't made explicitly available, the most specific actual type of an expression can't always be inferred. Which means that either (a) we don't do static checking because we don't want those blasted type error or syntax error messages anyways, or (b) there are cases where it is impossible for the compiler to statically type-check the program. In the (a) case, well, damn the torpedoes, full speed ahead, and I'll just make sure I don't fly your airline or do banking with your bank. In the (b) case now either you've lost type-safety (which I claim is good and worthy of our respect,) or you have to start inserting clues about types of expressions back into your program. Oh, but wait, that's sooo difficult, we can't do that. We'll just let it run... (tick, tick, tick...) |> |> > I want a computer which does EXACTLY what I tell it, really fast. |> If what |> >I tell it to do doesn't or might not make sense, I want it to let me |> know |> >so I can clear up any ambiguity as early as possible, rather than |> letting it |> >find it out later (rush hour at a busy airport, suddenly over 256 |> planes are |> >in the airspace, and some program writes past the end of an array, |> blasting |> >away the stack, crashing the ATC computer,) or make some guess ... |> |> A very good reason for runtime type checking. |> Or proving your algorithms and implementations before putting them in life-critical situations. |> > This is tiring. If you don't agree with me, that's OK, but I hope you |> >stay in the research world rather than producing software which people |> >pay for and expect to work reliably. But if you're going to claim that |> >this approach of dynamic typing increases {productivity, quality, |> >performance, correctness, robustness}, I'll continue to differ with you. |> |> I must admit that following your line of thought (or attempting to-- |> I'm not sure I succeeded) is tiring for me as well. I should point |> out that I am not in the research world, and work very hard to see |> that what people pay for works well. And while I disclaim knowledge |> of what you think dynamic typing is, I do claim that type as a |> property of DATA as opposed to a property of a NAME does increase |> productivity, quality, correctness as well as robustness. |> |> In fact, I claim that assigning types to names, independent of the |> assigned values, is a akin to side-effect driven programming. I |> realize that it is necessary at a low level. I don't see it as a |> productivity boost. I claim it doesn't. I also write software for a living that people depend on running their business. I've worked with languages where the programmer has the option of being able to specify that only certain values are permitted in a given context, and ones where looser bindings are permitted. There is no doubt in my mind that in the environments where the ability to specify the types of values which can be associated with a given name in a given scope, the quality of software was higher and the debugging cycle was shorter than in an environment which had a looser typing scheme and where I spent a few too many late nights tracking down where what was eventually an obvious typing error occurred. The problems occurred exactly because of the problems I've been outlining, and they would not/could not have occurred in a language where names are only permitted to be bound to certain types of values. The error would have been reported during compilation with a hopefully expressive message which would have explained the difficulty.) If you can remember all those details so that when modules evolve you can feel quite certain that interfaces won't be broken, I congratulate you. It's just too bad that you're using an environment where your skill and expertise are wasted debugging rather than designing and implementing. (I really don't understand the motivation. Is there some amazing wonderful power that you get out of dynamic typing? Other than being able to ask for bigger hardware and personnel budgets when you need bigger computers and more people to maintain your software? I don't see the costs of static typing being high, and the benefits are numerous. I'm trying to see where this claimed productivity and quality gain came from. Please enlighten me!) Sorry for rambling, it's past my bedtime. |> |> |> Raul Rockwell |> ------------------------------------------------------------------------ I'm saying this, not Digital. Don't hold them responsibile for it! Michael J. Grier Digital Equipment Corporation (508) 496-8417 grier@leno.dec.com Littleton, Mass, USA Mailstop OGO1-1/R6
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/15/91)
In article <1991Mar14.183323.27020@engage.enet.dec.com> grier@marx.enet.dec.com () writes: > In article <626@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David > Gudeman) writes: > |> In article <1991Mar13.163629.12630@engage.enet.dec.com> > |> grier@marx.enet.dec.com writes: > |> ] In mathematics, it makes NO sense to talk about applying a > |> function > |> ]or operation to a symbol unless the symbol is known to be in the > |> ]domain of the operation/function. I.e. writing something like "for > |> all > |> ]x, exp(x) is greater than zero" is nonsense. > |> Nonsense. Anybody with a clue immediately understands that x is > |> restricted to values in the domain of exp. > Wrong. No, you're wrong. Suppes, for example, defines functions so that you can say ``The set of x such that exp(x) is smaller than or equal to zero is empty.'' You don't have to qualify x as ``x in the reals'' for the statement to make sense and be perfectly correct. What this has to do with dynamic typing is beyond me. ---Dan
gudeman@cs.arizona.edu (David Gudeman) (03/15/91)
In article <1991Mar14.183323.27020@engage.enet.dec.com> grier@marx.enet.dec.com writes: ]In article <626@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David ]Gudeman) writes: ] ] ...If you consider a proof to have some static notion of ]correctness (which is exactly what they are,) this kind of error is ]absolutely wong in a formal exhibition of the proof. It depends on your mathematical tradition. There are lots of people who don't worry about that stuff (although the current vogue may tend toward the picky side). ]|> In the first place, the read() only produced ints and floats, and the ]|> return value was used in a context where either was a legal value. In ]|> the second place, if the read() function were defined to return other ]|> sorts of values and you wrote "x + read()" the problem would be in the ]|> program, not in the definition of "read()". ] ] Where is this knowledge known? Your arguments so far have been to ]not explicitly state the type of value accepted by or returned by an ]operator. And it still doesn't help when "Read()" is extended in the ]future to return strings, cats and dogs. No, I often explicitely state the types used by operators, I just don't tell the computer. The documentation for a function should give any relevant type restrictions. Whenever you change the way a fuction behaves, you (in general) have to check all the places it is used to make sure the change is OK. All static type checking provides is a compile-time warning if (1) you have missed any uses _and_ and (2) the function change was of the rare sort that changes the types. It doesn't seem very significant for that. ]|> Then you are going to need types "positive int" and "positive float"... ] ] That's silly. I'm arguing this in the light of subtyping, so you could ]have some sort of abstract subtype called "Algebraic" or "Number" where ]it has the "usual" operations defined. The point is the essential arbitrariness of type declarations. There is no good verification-based reason to distinguish between ints and floats and not distinguish between natural and integer, or between non-zero and with-zero. ] ReadNumber() would return a "Number" to permit at least type-safety. ]This is a case where the compiler would not have a choice but defer the ]binding of the operation invocation to actual code/methods until run-time, ]but it's still working in a statically typed type-safe environment. No, the environment is type-safe, but it isn't statically typed. There are now expressions whose types can't be uniquely determined at compile time. Dynamically typed languages are type safe, they just have less of the type checking done at compile time. ] She doesn't. That's why I argue for subtyping/inheritance/genericity/etc. ] ] The language is the framework and syntax to work in. It should be ]minimal and formal. Thank you for brightening my evening. I got a good chuckle out of that. Asking for subtyping, inheritence, genericity and etc. in a "minimal" language. What a card. ] Actually, formal proof is the only known way to ensure any measure of ]static correctness. Please stop. You're killing me. Where did you get this bizarre sense of humor? ] Once you've built up a large type library, the chances that any one ]person has a detailed gestalt of the whole thing is very unlikely. Therefore, ]unless you explicitly restrict the type of a value you receive, you can only ]assume that it's any type. (Whew. Back to serious stuff.) You generally don't build up large type libraries like that with dynamically typed languages. There are a few generic aggregate types that serve for most data structuring purposes. You don't have to implement stacks, hash tables, concatenatable arrays or other things. ] Again, one more time, with feeling, my concern is with large software ]systems. Again, I don't believe that required static typing is in any sense, in any software system, more reliable than dynamic typing. ]... I'm responding largely to the claims that ]some testing by (a) people who know the system or (b) people who are ]likely to only push the buttons that the (a) folks told them to are going to ]ensure anything. Absolute correctness no. But the level of security you get from testing is so far above any you might get from static typing, that the static typing is insignificant. ] Yup, and it's going to cost when we have to maintain those ]systems for the next 5-20 years. On the contrary, that's where the advantages of dynamically typed languages really shine. The programs are much more maintainable and "evolvable". -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
cs450a03@uc780.umd.edu (03/15/91)
>In article <14MAR91.22372006@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: > I don't see what point you're trying to make. If these aren't >take a look at most any object-oriented system designed in the last >decade or so with strong typing and parametrized types. The points I'm trying to make are that strong typing is a good thing, that dynamic type checking can be a good thing, and that two functions with different domains may have a subdomain in common. I'm claiming that a function's domain is the basis for typing. I'm claiming that there is no need for me to declare the type of result of function F if the result of function F is also the result of some other 'sub-function' G which has results of some known type (e.g. aggregates of 0 or more non-negative integers with a maximum value somewhere below a billion). I'm claiming that there is no need for me to declare that argument X to function F must have type K, if argument X is also an argument to function H which only accepts type K. I see no reason this couldn't be put into a compiler. Well, actually, I use a compiler which does this sort of thing. I rarely use it, unless I find code that's eating up cpu doing type checking/bounds checking. Also, there are cases where compiled code has worse performance than interpreted code (the interpreter has had some recent changes, since the release of the compiler). Incidentally, I have no objection to allowing declarations (e.g. restrict function F's argument X to single values, no aggregates). I prefer those declarations to be used sensibly, though. For example, if F is applied to datum Y, which has N single values, apply F n times and return an aggregate of n results). I do not see this as being in any way ambiguous. > (In direct answer, picking a syntax, perhaps "contains(y, x)", >"select_first_n(seq, 5)", "Object", huh? let's keep pointers out of this.., >assuming that the Table type is parametrized ala "Table[KeyType, >ValueType]", it's the type specified for KeyType when the particular >Table value instance was created.) Why keep pointers out of it? Why must I keep types independent of the typed information? You are going to waste my time issuing bean-certificates if you force me into that kind of situation. >|> Why should I limit assignment (name-association) based on the domain >|> of, for instance, addition? > > I don't know, why should you? I think you're trying to twist what I'm >saying somehow but I don't see your point. Perhaps it's your terminology. >I'll spell it out. > > If in some scope you have a variable, let's call it X, which is >declared to be of type "Number" (presuming all the types with >algebraic-type operations fall under there for the sake of argument,) >the compiler should ensure that any value I attempt to bind >X to is compatible with the type of X. Ok, let me restate myself. If X is going to be applied to function Y which only accepts numbers as arguments, then consider X to be declared type Y. If you are in an interpretive environment (reasonable if you are adding functionality on a day-to-day basis), it is more efficient to wait till X - Y closure to check type. In a compiler, you do not gain anything by REQUIRING that I declare that X can only be a number. > I can think of a better reason not to have runtime type checking for >log: if I'm doing it quite a bit, the runtime costs are high. Wasn't that >part of the whole original premise or why David Gudeman made his >original postings? They might be high, especially if you aren't working with aggragates. That's what compilers are for. That is, to eliminate redudant operations where possible, including redundant type checks. > I believe that Ada's model here is correct. No correct program may >depend on exceptions for its operation. (Implying that if you have >a correct program, you can turn off all the nice bounds-testing and >such and end up with a nice and *fast* program. Commercial Ada >compilers from both Rational and Digital do this.) I'd hate to have to do development work in Ada. I wouldn't mind having Ada available to RE-write time critical sections of code. Me: >|> >|> Finally, to Mr. Grier, who posed the rhetorical problem about trusting >|> >|> software in critical systems which might have latent bugs: would you >|> >|> really trust such software if it had never been tested? Would it make >|> >|> you feel safer if each type of data required seperate chunks of code, >|> >|> with the associated tests and branches and variant storage mechanisms? Grier: >|> > Yes and no. No first. >|> > >|> > "No", because that's why I think that subtyping and inheritance >|> >is so wonderful. If there's an obvious way to specialize an >|> >operation to a subtype, it *is* clearer and still absolutely >|> >statically correct to apply the supertype's operation to a value >|> >of the subtype. Me: >|> No this makes you feel safer? Or no it doesn't make you feel safer? Grier: > Beings I discusses my thoughts on both counts, does it matter other >than you wanting to pick nits? My apologies for being sarcastic. I keep forgetting how time and distance affect meaning. What I meant was, I asked a simple yes/no question (ok, somewhat rhetorical) and the first few paragraphs of answer did not address what I thought was the key issue. This apology also good for my remarks after the "Yes" half of the answer. .. >|> And how is a compiler supposed to prove that a program fulfills its >|> purpose? At best, the compiler can prove that the program can be >|> compiled. >|> > > Re-read my previous posting today. (well, yesterday at this time) I did not see anything that answers this question. Raul Rockwell
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/15/91)
In article <651@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > The point is the essential arbitrariness of type declarations. There > is no good verification-based reason to distinguish between ints and > floats and not distinguish between natural and integer, or between > non-zero and with-zero. So? If you want to distinguish between the naturals with and without zero, use Ada. > ] Once you've built up a large type library, the chances that any one > ]person has a detailed gestalt of the whole thing is very unlikely. Therefore, > ]unless you explicitly restrict the type of a value you receive, you can only > ]assume that it's any type. > (Whew. Back to serious stuff.) You generally don't build up large > type libraries like that with dynamically typed languages. There are > a few generic aggregate types that serve for most data structuring > purposes. You don't have to implement stacks, hash tables, > concatenatable arrays or other things. Unfortunately, the programming world is full of naturally complex data structures. You can point to at least half of the typically fifty-odd elements of the UNIX inode structure, for example, and give good reasons why those elements must be in the structure, even though they require all sorts of special operations. Even simple data structures like Patricia require dozens of operations for a complete implementation. > ] Again, one more time, with feeling, my concern is with large software > ]systems. > Again, I don't believe that required static typing is in any sense, in > any software system, more reliable than dynamic typing. Fer cryin' out loud, who cares? I don't think any language designer will take pains to *stop* you from using dynamic typing; if enough people agree with you that dynamic typing is useful, then you can distribute your library and be done with it. Nobody's stopping you from writing your software with dynamic typing in practically any language. Just stop pestering the people who prefer static typing in the same languages. > On the contrary, that's where the advantages of dynamically typed > languages really shine. The programs are much more maintainable and > "evolvable". This smacks of religion. Have you compared dynamically typed and statically typed programs solving similar problems? Have you observed the programs over their useful life and seen how much programmer effort went into them? ---Dan
sfk@otter.hpl.hp.com (Steve Knight) (03/15/91)
gudeman@cs.arizona.edu (David Gudeman) writes: > I thought you had to declare the types of functions in ML... Sort of. ML is a very interesting strongly typed language in which the types of expressions can often be inferred without type-annotations. Scott Schwartz writes: > % sml > Standard ML of New Jersey, Version 0.66, 15 September 1990 > - fun foo x = x + 1; > val foo = fn : int -> int > - fun bar x = x + 1.0; > val bar = fn : real -> real Despite what follows, I commend ML to anyone who is interested in the development of strongly typed languages. But enough of the Mr.Nice stuff and onto the language bashing you're all waiting for. { .. DISGRUNTLED ML PROGRAMMER ALERT ... WARNING ... <FLASH!> ... WARNING ... } - foo x = x + 1; val foo = fn : int -> int Ah yes. True enough. Good job the example wasn't this, though .... - fun foo x = x + x; Error: overloaded variable "+" cannot be resolved It was equally fortunate that this example wasn't used either .... - datatype Foo = Bool of bool | Int of int | Fn of int -> int; - fun foo (x as Fn _, y as Fn _) = false = | foo ( x, y ) = x = y; Error: rules don't agree (equality type required) You see it's vitally important that this function is never executed. Because ML doesn't allow comparison of functions, which is arguable, it cannot allow the comparison of data-types which include functions. Even when the programmer has specifically checked against that possibility ..... Another, obviously wicked, faulty program that the type-checker fortunately throws out on its ear before I do myself any damage is ... - val foo = [false, 0]; Error: operator and operand don't agree (tycon mismatch) Notice the very dangerous consequences of using a list containing a boolean and an integer? This is just one of the many cases where the type system of ML is too conservative, in order to provide the user with type inference. - datatype IntOrBool = Int of int | Bool of bool; - val foo = [Bool false, Int 0]; Just look how ML doesn't need type declarations! I hope the intelligent reader is beginning to get an idea how this splendid type inference is achieved. Polymorphism is very nice, too. I thought I'd construct an updateable reference to the empty list. Whoops! (ML is primarily a functional programming language. The interaction between imperative data structures and polymorphism isn't all one might desire.) - val x = ref []; Error: nongeneric weak type variable x : '0S list ref Gosh. A jolly good job I was protected there. That could have led to a terrible run-time error. The other elegant aspect of the ML type inference system is its ability to resolve the most general type of an equation. In this function "f", the first parameter is never used. So the type inference system deduces that it can be any value it likes ... <clank!> - fun f x y = if y = 0 then 0 else f f (y-1); Error: operator is not a function As you can see, modern programming languages that support polymorphic type-checking and type-inference give you all the security and performance enhancements of a strongly typed language and all the flexibility and convenience of a dynamically typed language. Yes you certainly get excellent performance from ML. It is not possible to write a polymorphic hash-function. So you can't write polymorphic hash-tables. So you can't write reusable-code for hash-tables. So people use linked lists. Good job it goes fast. (Actually, this is more than a little unfair. You only have to write out hash-functions for each type used in a hash-table. This isn't *really* acceptable but you can sort of live with it. Also some folks, like us, use binary trees rather than linked lists, which makes the comparison less ludicrous.) { END OF MAD RAVINGS ... } My belief is that, to date, there's been no programming languages that gives the obvious benefits of strong typing without some significant penalty. The penalty is typically both superfluous type-declarations in the program and the elimination of valuable programming idioms. (Furthermore, the issue of performance is greatly complicated by the elimination of these idioms.) Unsurprisingly, I am inclined to believe that strong Vs dynamic typing is an unresolved debate. In practical terms, it is still horses for courses. When writing low-level code, strong typing is (in my view) invaluable. When writing in "very" high-level languages, such as Lisp or ML, the penalties are less tolerable (in my view) and, on balance, I prefer dynamically typing. One of the under-explored regions of this topic is that of heuristic type- checking for dynamically typed languages. My belief is that this hybrid approach can be made effective enough to be useful. I know the Scheme folks have made progress in this area but I've not kept up to date on it. Steve
wright@datura.rice.edu (Andrew Wright) (03/15/91)
I would like to see a concise summary of the claimed advantages of dynamic typing. The claimed advantages of static typing are numerous and often argued (verification, efficiency, documentation, ...) but the advantages of dynamic typing are less often discussed. To start: *** expressiveness (probably the single largest advantage) * terseness (in that declaration of types is not required). This is a mild point given the advances in type inference, as witnessed by languages like ML. Andrew Wright Rice University
peter@ficc.ferranti.com (Peter da Silva) (03/16/91)
In article <24547:Mar1506:28:2591@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: > No, you're wrong. Suppes, for example, defines functions so that you can > say ``The set of x such that exp(x) is smaller than or equal to zero is > empty.'' You don't have to qualify x as ``x in the reals'' for the > statement to make sense and be perfectly correct. That's because "real exp(real);" is programmed into everyone's head. If you have "complex y;" then "y <= 0;" is a syntax error. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you debugged your wolf today?"
gudeman@cs.arizona.edu (David Gudeman) (03/16/91)
In article <26221:Mar1510:17:4991@kramden.acf.nyu.edu> Dan Bernstein writes: ]In article <651@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]> The point is the essential arbitrariness of type declarations. There ]> is no good verification-based reason to ... ] ]So? If you want to distinguish between the naturals with and without ]zero, use Ada. The point is the essential arbitrariness of type declarations. That means the point is not (1) that I want to distinguish between the naturals with and without reals, and not (2) that I want to eliminate type declarations alltogether. Just that arguments that type declarations are necessary for security are problematic if for no other reason than because they are taking a system designed for A and claiming that it's purpose is B (where A is efficiency and B is security). As I said before, I believe this is a historical accident. Weak typing actually does cause security problems, and when people started making static typing stronger for program security, they started to believe that the typing itself gives program security. This is not the case. It was necessary to make static type checking stronger to overcome the problems that static type checking caused in the first place -- lack of type security. Dynamically typed languages never had this problem. ]> ... You generally don't build up large ]> type libraries like that with dynamically typed languages. ]Unfortunately, the programming world is full of naturally complex data ]structures. I did say "generally". However the point is well taken that built-in aggregate types can't handle everything, and that you still need ADT's. So let me answer the original question in another way: ]> ] Once you've built up a large type library, the chances that any one ]> ]person has a detailed gestalt of the whole thing is very unlikely. ]> ]Therefore, unless you explicitly restrict the type of a value you ]> ]receive, you can only assume that it's any type. Yes, that is correct. You generally have to assume that the inputs to a function can be any type. That means that you have to write explicit type checks in some circumstances. I will even admit that writing these explicit checks generally requires more time than declarations would, since you have to explicitly do something if the type is wrong (usually exit with an error message). However, it seems to me that the places where this is necessary are rare since most operations on ADT's seem to be generic (on arguments other than the aggregate itself). Also, these explicit type checks generally only have to be written once and never changed, unlike static declarations that may have to be changed when you upgrade the program. ]> ] Again, one more time, with feeling, my concern is with large software ]> ]systems. ]> Again, I don't believe that required static typing is in any sense, in ]> any software system, more reliable than dynamic typing. ] ]Fer cryin' out loud, who cares? That's what this whole argument is about. ]... Just stop ]pestering the people who prefer static typing in the same languages. Calm down, Dan. I've already said it 4 or 5 times, but here it is once more: I am not arguing against type declarations. I am arguing against _required_ type declarations and against the argument that static type checking increases program security. I am not arguing that static typing has no uses at all, in fact I have said several times that it is important for efficiency. I've also mentioned once or twice that as an option it can make debugging easier. The only people who should feel pestered by me are people who think that static typing is necessary for program robustness and who think that programmers should be forced by the language to do things in a certain way. ]> On the contrary, that's where the advantages of dynamically typed ]> languages really shine. The programs are much more maintainable and ]> "evolvable". ] ]This smacks of religion. Have you compared dynamically typed and ]statically typed programs solving similar problems? Have you observed ]the programs over their useful life and seen how much programmer effort ]went into them? I haven't done anything resembling an experiment, no. My opinions are based on experience and observation (of several large systems). These things seem rather self evident if you accept the premise that the amount of work needed to maintain and upgrade a program is roughly related to the size of the program and to the number of places where a given piece of information has to be duplicated. Programs in dynamically typed languages are generally half to a tenth the size of programs in statically typed languages that do the same thing. Also, many statically typed languages require duplicate type information in two different places (a declaration and a definition), but dynamically typed languages have no similar maintenance problem. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/16/91)
In article <1991Mar15.062756.3781@engage.enet.dec.com> grier@marx.enet.dec.com writes:
]
] I believe that Ada's model here is correct. No correct program may
]depend on exceptions for its operation. (Implying that if you have
]a correct program, you can turn off all the nice bounds-testing and
]such and end up with a nice and *fast* program. Commercial Ada
]compilers from both Rational and Digital do this.)
Good grief. You don't think dynamic typing is safe enough but you are
willing to turn off bounds checking? That is going to make your
programs a _lot_ more dangerous than any program in a dynamically
typed language.
]... Loose induction and
]random sampling don't make for proofs.
No, in the realm of software they give you much better reliability
than proofs. I didn't want to get into another topic, but this just
too much. Vendor A offers a product that has never been tested but
that has an computer-verified proof of correctness. Vendor B offers
an identical product that has never been proved correct but that has
been through the normal testing process. Which one do you want? Any
sane person would pick the product from vendor B.
Incidentally, there is no reason why you can't have computer verified
proofs of correctness combined with dynamic typing. Of course, just
as for statically typed languages, it wouldn't be worth the effort to
write the proofs for any but a tiny number of programs.
] Once again, this is a problem I see in large systems evolving over time.
]My motivation isn't so much to prove correctness as to ensure that changes
]and growth don't invalidate other algorithms' implementations. My
]extending "Read()" to return baby names in addition to numbers broke
]David Gudeman's program which assumes they're numbers, unfortunately.
If you change read() in such a way as to invalidate its original
documentation then you should have gone through all code that uses the
fuction and made sure they were still correct. If you didn't do so,
then you did something stupid, and there has never yet been a
programming language made that can prevent bugs due to stupidity. And
after you changed the function you should have tested the system
again, and that should have found the problem. And if you didn't find
the problem that way (then your testing was slipshod, but also...),
then the worst that could happen during the actual use of the software
is for the system to detect the error and report it, failing to do
what was expected -- a minor problem for the vast majority of
applications. A lot worse can happen if you turn off array bounds
checking, and nobody seems overly concerned about that.
] I want the system to be able to recognize these problems and prevent
]them from being made into a running system.
Yeah, I want the system to recognize all my errors at compile time.
I'm just not willing to give up all the expressiveness I'd have to
give up to make that possible.
](a) we don't do static
]checking because we don't want those blasted type error or syntax
]error messages anyways, (b) there are cases where it is impossible
]for the compiler to statically type-check the program.
]
] In the (a) case, well, damn the torpedoes, full speed ahead, and I'll
]just make sure I don't fly your airline or do banking with your bank.
The banking example is a non-problem. I don't believe its possible to
get an accounting error due to lack of static type checking. If you
get a type error the worst that can happen is for the program to
abort. As to the airplanes, a program abort might be a little more
inconvient, but the program should be written in such a way that it is
protected from that even if type errors occur.
] In the (b) case now either you've lost type-safety (which I claim is
]good and worthy of our respect,) or you have to start inserting clues
]about types of expressions back into your program. Oh, but wait,
]that's sooo difficult, we can't do that. We'll just let it run... (tick,
]tick, tick...)
Dynamically typed languages are type-safe. And how many times do I
have to say that it isn't simply the writing of declarations that I
object to?
]There is no doubt in
]my mind that in the environments where the ability to specify the
]types of values which can be associated with a given name in a given
]scope, the quality of software was higher and the debugging cycle
]was shorter than in an environment which had a looser typing scheme
So who is arguing that? It isn't the _abilty_ to specify types that
is the problem, it is the _requirement_ of specifying types. A
requirement that leads to either (1) weak typing -- I think you will
agree that that is a problem, or (2) the further requirement of
excessively detailed declarations and excessive interdependency of
modules -- which I claim are more problems. With optional type
declarations and the ability of the language to insert runtime type
checks you get better reliability.
]and where I spent a few too many late nights tracking down where
]what was eventually an obvious typing error occurred.
There are a few dynamically typed languages with inadequate error
messages or debugging facilities. That is a problem with the
language (or implementation), not the concept of dynamic typing.
] If you can remember all those details so that when modules
]evolve you can feel quite certain that interfaces won't be broken,
]I congratulate you. It's just too bad that you're using an environment
]where your skill and expertise are wasted debugging rather than
]designing and implementing.
I spend a lot more time debugging when I program in C than when I
program in dynamically typed languages.
] (I really don't understand the motivation. Is there some amazing
]wonderful power that you get out of dynamic typing?
] I don't see the costs of static typing being high, and the
]benefits are numerous.
That's because you you don't see the costs at all. You are so used to
it that you think the problems caused by static typing are fundamental
to programming, and don't see the true cause.
] I'm trying to see where this claimed
]productivity and quality gain came from. Please enlighten me!)
Mostly the fact that you have to write a lot less code to get the same
functionality.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
cs450a03@uc780.umd.edu (03/16/91)
Steve Knight writes: ... (ML examples ommitted) ... >My belief is that, to date, there's been no programming languages that >gives the obvious benefits of strong typing without some significant penalty. >The penalty is typically both superfluous type-declarations in the program >and the elimination of valuable programming idioms. (Furthermore, the issue >of performance is greatly complicated by the elimination of these idioms.) > >Unsurprisingly, I am inclined to believe that strong Vs dynamic >typing is an unresolved debate. In practical terms, it is still horses for >courses. When writing low-level code, strong typing is (in my view) >invaluable. When writing in "very" high-level languages, such as Lisp or >ML, the penalties are less tolerable (in my view) and, on balance, I prefer >dynamically typing. Well, yess.. Since you didn't define what you meant by Strong and Dynamic typing, and since I don't think those definitions conflict in the way they have been used in this subject thread, well... hmm... If you say strong typing is what you presented in the ML examples, what would you say dynamic typing is? Also, I believe that some of the 'limitations' of ML are tolerable. For example, I don't compare functions, I compare their character representations (not that I need much of that, except for library maintainence). Also, I almost always live with homogenous data structures (all elements of the same type)--there are a lot of performance advantages to this (e.g. 10000 elements need only one type header, which only need be checked once per pass over the data). Finally, though this is somewhat of a self-imposed limitation, I don't use a lot of the facilities that are available, unless absolutely necessary (in other words, since the primitives will already deal nicely with those 10000 numbers, or whatever, I don't bother with a loop which would have to (a) extract each element, and build a type/header for it, and (b) have the overhead of calling primitives 10000 times). Of course, I don't use ML, so I don't know how applicable these comments are to programs written in ML. Your mileage may vary. Void where taxed or prohibitted. Prices may be higher west of the Mississippi. Batteries not included. NO WARRANTEE (This information provided without warra... Raul Rockwell
sommar@enea.se (Erland Sommarskog) (03/17/91)
Also sprach David Gudeman (gudeman@cs.arizona.edu): >All dynamically typed language I know have this feature. In fact for >most of these languages there are no programs that have undefined >behavior, I don't know whether any statically typed language can >make that claim. So when the types don't match, you crash. Sure, this is well- defined, but in many application it is just as unacceptable as undefined behaviour. If variables never can be uninitiated in your dynamicly typed language, I guess their behaviour can be well-defined. -- Erland Sommarskog - ENEA Data, Stockholm - sommar@enea.se
campbell@redsox.bsw.com (Larry Campbell) (03/17/91)
In article <25381:Mar1221:07:3891@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: -In article <609@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: -> Why should a programming language be expected to catch this type of -> error at compile time, and not some other class of errors? Why are -> type errors so special? Given the difficulty of completely testing all possible functions in any large software product, the difference between catching the error at compile time and catching it at runtime is the difference between YOU discovering the error during development and the CUSTOMER discovering the error in the field. -- Larry Campbell The Boston Software Works, Inc., 120 Fulton Street campbell@redsox.bsw.com Boston, Massachusetts 02109 (USA)
jpiitulainen@cc.helsinki.fi (03/18/91)
In article <2400034@otter.hpl.hp.com>, sfk@otter.hpl.hp.com (Steve Knight) writes: > One of the under-explored regions of this topic is that of heuristic type- > checking for dynamically typed languages. My belief is that this hybrid > approach can be made effective enough to be useful. I know the Scheme folks > have made progress in this area but I've not kept up to date on it. You might be interested in the following paper: Olin Shivers, "Data-Flow Analysis and Type Recovery in Scheme", March 30, 1990, CMU-CS-90-115, to appear in Peter Lee (ed.), _Topics in Advanced Language Implementation_, MIT Press
cs450a03@uc780.umd.edu (03/18/91)
Dr. Walker writes: >In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu >(David Gudeman) writes: >> Mathematical notation is generally >>closer to dynamically typed languages than to statically typed >>languages. > > & it was invented and had evolved long before the whole modern >problem of trying to describe problems and algorithms in a precise way >for computer consumption. Such examples as we have of attempts at >precision in maths [eg, by Russell] are not very convincing. We write >sloppy maths because "we all know what we mean" -- I don't think that's >a very good model for computer languages. As one of my profs had to continually explain to me (when I made similar complaints about mathematical notations), it's primarily a matter of definitions. In any event, the number of statements you use to make a declaration has very little to do with how 'mathematical' you are being. I've seen many, many statements of the form 'x element of set y, x takes on value z.' ... Quite similar to the sort of things required in a statically typed language. On the other hand, just because a mathematical statement doesn't fit within the confines of some brand C statically typed language is no reason to call it sloppy. Mind you, I'm not trying to argue for Russell's stuff... I'd hate to try and implement code for it :) >>With static typing you need a great deal of information at compile >>time to be able to guarantee strong typing. This has two >>consequences: (1) you have to limit the forms of expressions to some >>set for which you know a type-checking decision procedure, and (2) you >>have to acquire type information somewhere. > > Ie, (1) you have to know what your expression is intended to do, >and (2) you have to use variables in a disciplined way. I don't find >these "consequences" either irksome or undesirable. When I write programs >in dynamically typed languages, I try hard to follow the same precepts. Indeed, but I find generalizing quite difficult in the statically typed languages I've run across. There is, of course, C's approach (declare a prototype array, alloc some memory, declare it to be of type 'prototype array', and ignore the phony array boundaries), but that's just too much busy work for me. Then there's the wonderful idea of allocating a gob of memory and hoping it's enough. Then there's the technique of re-compiling the program for each instance of the problem (a real speed demon if you need to run several hundred thousand cases of each 'instance'). > There is a place for dynamic typing (I enjoy writing shell >scripts!), and a case for rapid prototyping, but there is also a >case for traditional declarations; there is no need for either >"camp" to knock the other. Quite true. However, it's a bit ironic that things as inefficient as shell scripts (with the attendant overhead of massive file operations and forks and so on) go hand in hand with statically typed languages (with claims of awesome efficiencies). It's even more ironic when a program written in a statically typed language will rely on heavy file manipulation because they can not deal with memory with enough abstraction. I'm also rather amused (if only it weren't so painful) by the amount of memory required by statically linked programs. And by things like fixed sized buffers, with system calls that haven't a clue as to that size. Ah... the wonderous maintainability of statically typed code... ;) *sigh* :( Raul Rockwell
brm@neon.Stanford.EDU (Brian R. Murphy) (03/18/91)
In article <1991Mar17.161210.5574@cc.helsinki.fi> jpiitulainen@cc.helsinki.fi writes: >In article <2400034@otter.hpl.hp.com>, sfk@otter.hpl.hp.com (Steve Knight) >writes: >> One of the under-explored regions of this topic is that of heuristic type- >> checking for dynamically typed languages. My belief is that this hybrid >> approach can be made effective enough to be useful. I know the Scheme folks >> have made progress in this area but I've not kept up to date on it. > >You might be interested in the following paper: > Olin Shivers, "Data-Flow Analysis and Type Recovery in Scheme", > March 30, 1990, CMU-CS-90-115, to appear in Peter Lee (ed.), > _Topics in Advanced Language Implementation_, MIT Press You might also be interested in: A. Aiken and B. Murphy, "Static Type Inference in a Dynamically Typed Language", in {\em Proceedings of the Seventeenth Annual ACM Symposium on the Principles of Programming Languages}, Orlando, 1991, pp. 279-290. While this is specifically about type inference for the functional language FL (successor of FP), it's also easily applicable to a functional subset of Lisp, and, with a bit more effort, to a non-functional subset (I think---haven't actually done it). It hinges a lot on our representation of types, to be described in an upcoming paper (also described somewhat less completely & intelligibly in my 1990 MIT MS thesis, "A Type Inference System for FL"). -Brian
gudeman@cs.arizona.edu (David Gudeman) (03/18/91)
In article <1991Mar17.131413.13312@redsox.bsw.com> Larry Campbell writes:
]
]Given the difficulty of completely testing all possible functions in any
]large software product, the difference between catching the error at compile
]time and catching it at runtime is the difference between YOU discovering the
]error during development and the CUSTOMER discovering the error in the field.
Millions of lines worth of code written in dynamically typed languages
is in regular use throughout the word, and no general problems with
robustness have been noticed. Until there is some evidence strong
enough to counter this wealth of experience, I wish people would stop
making unsupported statements like the one above.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/18/91)
In article <-B0A9_3@xds13.ferranti.com> Peter da Silva writes:
]
]That's because "real exp(real);" is programmed into everyone's head. If
]you have "complex y;" then "y <= 0;" is a syntax error.
That's the point! If you _don't_ write "complex y", and somewhere you
have "y <= 0", then you know that y is not complex. There is no
pressing reason why you should have to write "real y" in most cases.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/18/91)
In article <693@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > Programs in > dynamically typed languages are generally half to a tenth the size of > programs in statically typed languages that do the same thing. I don't believe you. Give an example. ---Dan
gudeman@cs.arizona.edu (David Gudeman) (03/18/91)
In article <1991Mar14.151707.11686@maths.nott.ac.uk> Dr A. N. Walker writes: ]In article <602@optima.cs.arizona.edu> gudeman@cs.arizona.edu ](David Gudeman) writes: ] ]We write ]sloppy maths because "we all know what we mean" -- I don't think that's ]a very good model for computer languages. I object to the term "sloppy". We often don't give unnecessary and redundant information because it isn't needed. I think it's an excellent model for computer languages. ]> Static typing ]>originated, as near as I can determine, with low-level languages like ]>Fortran an Algol that were little more than glorified assemblers. ] ] [I assume that Algol 60, rather than older or modern versions, ]is meant.] This statement is just historically ignorant. No, I was refering to the earliest Algol tradition. ] Fortran did not, for the most part, ]have declarations at all; I said "static typing", not "type declarations". Early FORTRAN had static typing but some of the declarations were implicit. ] Well this is a matter of semantics [:-)]. The C fragment ]"int a[10]; a[23] = 17;" might, in many implementations, do something ]arbitrary to memory, but in my opinion it contains an error. You are just arguing for array bounds checking -- arguably a form of dynamic typing. ] Making ]the behaviour undefined is [perhaps wrong-headedly] a convenience ]for the compiler writer. Not just a convenience. It makes it _possible_ to generate faster code, not just easier. ] Would C become more strongly typed if the ]behaviour became defined in some way? Yes. ]>With static typing you need a great deal of information at compile ]>time to be able to guarantee strong typing. This has two ]>consequences: (1) you have to limit the forms of expressions to some ]>set for which you know a type-checking decision procedure, and (2) you ]>have to acquire type information somewhere. ] Ie, (1) you have to know what your expression is intended to do, ]and (2) you have to use variables in a disciplined way. Your (1) is meaningless. Of course you have to know what an expression is intended to do in any language. This has nothing to do with when type checking is done. Your (2) is a subjective judgement that can mean anything. Static typing certainly does not enforce any sort of discipline in the way you use variables unless by "discipline" you mean "restricted to a single language type". Then (2) becomes a tautology: "static type enforcement forces you to use static types." There is no inherent advantage (other than efficiency) to following the "discipline" of restricting all your variables to a single language type. ] I don't find ]these "consequences" either irksome or undesirable. When I write programs ]in dynamically typed languages, I try hard to follow the same precepts. I don't find your "consequences" undesireable either (using my own definition of "disciplined"). I find the unrelated two consequence I listed to be extremely undesirable. ] Type *consistency* is indeed the compiler's job, but I don't ]find it unreasonable that I should document my identifiers. I find it unreasonable that I should be forced by some language designer who has no idea what I'm trying to do, to document my variables in a specific way which may be completely worthless in the specific task at hand. And not just identifiers. In a strongly typed, statically typed language you have to declare the recursively complete type of every data location reachable in the program. ] There is a place for dynamic typing (I enjoy writing shell ]scripts!), and a case for rapid prototyping, but there is also a ]case for traditional declarations; there is no need for either ]"camp" to knock the other. In the first place, dynamic typing is useful for full-scale working programs, not just prototypes. In the second, I'm not knocking the usefulness of _optional_ type declarations. I'm trying to point out that there better alternatives to enforced static typing, and that dynamic typing is not "dangerous". There is absolutely no evidence that dynamic typing is in any way unsafe, and considerable evidence to the contrary. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/18/91)
In article <-B0A9_3@xds13.ferranti.com> peter@ficc.ferranti.com (Peter da Silva) writes: > In article <24547:Mar1506:28:2591@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: > > No, you're wrong. Suppes, for example, defines functions so that you can > > say ``The set of x such that exp(x) is smaller than or equal to zero is > > empty.'' You don't have to qualify x as ``x in the reals'' for the > > statement to make sense and be perfectly correct. > That's because "real exp(real);" is programmed into everyone's head. If > you have "complex y;" then "y <= 0;" is a syntax error. No, it isn't, at least not in the works of Suppes (and Tarski), but I was referring to the function exp on the reals. ---Dan
cs450a03@uc780.umd.edu (03/18/91)
Erland Sommarskog writes: >So when the types don't match, you crash. Sure, this is well- >defined, but in many application it is just as unacceptable >as undefined behaviour. Or you backtrack to some pre-defined point, with a warning, and continue from there. Not ideal, but hopefully saving significant work on the part of the user. Or if it's a known ill-conditioned case, maybe you can recover in some other way. Or, your type int is promoted to type float and you have a little floating point over-head. Or, if you really want, you crash... but in an interactive environment where you have the option of examining the situation right there (with files still opened and in the same state, for instance), possibly hand-patching the data and nursing it along to completion, possibly re-writing a function, popping back a few levels and re-starting, or possibly you dump core (though it's hardly necessary to call the file 'core'). Not, I'll admit, a requirement in a dynamically typed language (to support all these options), but much less challenging to implement than in a statically typed language. (Dynamic typing makes dynamic linking somewhat easier, I believe, and in any event, both seem easy in an 'interpretive' environment.) >If variables never can be uninitiated in your dynamicly typed >language, I guess their behaviour can be well-defined. yep. If you try and use it with no value, it'll probably (depending on your language, of course) be a run-time error, with all that implies (see above). On the other hand, you can probably also test it, to see if it has a value... And there are always code sequences that guarantee that the variable has a value. ********************************************************************** Repeat of one of Gudeman's arguments: When using run-time type checking, practically all type errors are caught in early stages of testing. Corollary I: The software must be tested. Corollary II: If the code is ill-conditioned, such that it is likely that invalid arguments will not show up during testing, the code is poorly written. (This last is not intended to be proved, but is more on the lines of a partial definition.) ********************************************************************** 'nuff said? Raul Rockwell
peter@ficc.ferranti.com (Peter da Silva) (03/19/91)
In article <17MAR91.21285518@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > I'm also rather amused (if only it weren't so painful) by the amount > of memory required by statically linked programs. Smoke and mirrors time... notice how "statically typed" has become "statically linked". The two concepts are orthogonal. Besides: -rwx--x--x 3 bin bin 29217 Sep 28 1988 /bin/sh -rw-r--r-- 1 root root 206380 Nov 8 12:02 libXlisp.a -rwxr-xr-x 1 root root 175889 Nov 8 12:11 xlisp And this is a "toy" language! -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (03/19/91)
In article <730@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > In article <-B0A9_3@xds13.ferranti.com> Peter da Silva writes: > ]That's because "real exp(real);" is programmed into everyone's head. If > ]you have "complex y;" then "y <= 0;" is a syntax error. > That's the point! If you _don't_ write "complex y", and somewhere you > have "y <= 0", then you know that y is not complex. There is no > pressing reason why you should have to write "real y" in most cases. But if you don't know that y is complex, you don't know if "y <= 0" is legal or not... working code or a bug. The only reason you can get away with this in mathematics is everyone has had a bunch of Fortran-style' default typing rules programmed into their head in college. And even so, different mathematical languages disagree and you have to introduce people to these new typing rules... -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/19/91)
Peter da Silva writes: >In article <17MAR91.21285518@uc780.umd.edu> cs450a03@uc780.umd.edu writes: >> I'm also rather amused (if only it weren't so painful) by the amount >> of memory required by statically linked programs. > >Smoke and mirrors time... notice how "statically typed" has become >"statically linked". Nahh... I was just griping... Raul
gudeman@cs.arizona.edu (David Gudeman) (03/19/91)
In article <2837@enea.se> Erland Sommarskog writes: ] ]So when the types don't match, you crash. Sure, this is well- ]defined, but in many application it is just as unacceptable ]as undefined behaviour. First of all, this "crash" cannot do arbitrary things like overwrite files. Second, it may not necessarily crash, there are other things that can happen, depending on the languages exception-handling facilities. Third, there are very few applications where a crash is as bad as undefined behavior. Undefined behavior can do all kinds of nasty things to the system. For example, referencing past the end of an array in C causes undefined behavior: void trouble() { int arr[4] int delete_flag = 0; ... arr[4] = 1; /* undefined behavior -- might set delete_flag to 1 */ ... if (delete_flag) delete_all_files(); ... } This is much worse than what you get with dynamically typed languages. The only place where an error stop can be this bad is in critical real-time applications, and such applications should have exception handling to avoid error stops. ]If variables never can be uninitiated in your dynamicly typed ]language, I guess their behaviour can be well-defined. I can't think of any dynamically typed language where the use of an unitialized variable causes undefine behavior, but there surely are some. I can think of several statically typed languages where it causes undefined behavior. In any case, undefined variable semantics is orthogonal to typing. I have no objection to compilers that issue errors for undefined variables in any language. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/19/91)
In article <H-2A982@xds13.ferranti.com> Peter da Silva writes:
]
]Besides:
]
]-rwx--x--x 3 bin bin 29217 Sep 28 1988 /bin/sh
]-rw-r--r-- 1 root root 206380 Nov 8 12:02 libXlisp.a
]-rwxr-xr-x 1 root root 175889 Nov 8 12:11 xlisp
]
]And this is a "toy" language!
And this is a bogus comparison. If you want to compare the sizes of
the tools, you have to add enough tools to give /bin/sh similar
functionality to the "toy" language. Xlisp is far from a toy, it is
more powerful than C (including C's standard libraries). It is just a
"toy" compared to other Lisp's.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/19/91)
In article <L-2A3G2@xds13.ferranti.com> Peter da Silva writes:
]But if you don't know that y is complex, you don't know if "y <= 0" is
]legal or not...
If you don't know the length of A, you don't know whether "A[i]" is
legal or not. So? You should know the length of A and you should
know the type of y. In someone elses code, if you see "y <= 0",
then you can assume that y is not complex. In you own code, if you
know that y is not complex you can write "y <= 0". There is no
ambiguity.
]with this in mathematics is everyone has had a bunch of Fortran-style'
]default typing rules programmed into their head in college.
No, the reason you can get away with this in math is that everyone who
has a clue knows that you can't compare magnitudes of complex numbers,
so if you are comparing magnitudes, the numbers must not be compex.
This is a trivial logical inference:
(1) y is complex implies "y <= 0" is not meaningful
(2) "y <= 0" is meaningful
therefore
(3) y is not complex
There is nothing sloppy or ambiguous about this reasoning.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
sfk@otter.hpl.hp.com (Steve Knight) (03/19/91)
In response to a claim (or observation) made by David Gudeman that "dynamically typed languages are generally half to a tenth the size of programs in statically typed languages" Dan Bernstein reasonably writes back. ] I don't believe you. Give an example. Of course, this gets us into the foolish realms of anecdotes and virility tests. However, just because a challenge is foolish... Well, perhaps it is sufficient to say that this exchange reminded me of an amusing incident a couple of years ago between myself and a friend of mine. (Who shall remain nameless; unless you're reading this, Tim.) After a rather boring stint at work, Tim into my office with a broad smile on his face -- evidently having completed an exceptionally useless task successfully. "Watch this," he said, and proceeded to demonstrate the capabilities of a simple program he'd prepared earlier while watching the C compiler dither over a small pile of files. This program read a file of syllables and printed out "passwords" composed from three random syllables. It was vaguely entertaining in that sort of Friday afternoon-going-on-evening, let's-out-for-a-pizza-later kind of way. Tim then showed me the source code. Now Tim, I should explain, makes no pretension of being a great C hacker (well, not then) but had composed the program in a very workman-like way. However, at several pages of code it seemed rather lengthy to me. I leaned over my workstation and said, "Oh, it would have been a bit easier in Pop11" and proceeded to <tappity-tappity-tap> for a few moments. (Pop11 is the dynamically typed language of this tale, by the way.) What emerged was the following few lines ... define program(); ;;; build a list of syllables lvars syllables = 'syllables'.discin.incharline.pdtolist; repeat 3 times syllables.oneof.pr ;;; print a syllable 3 times endrepeat; nl( 1 ); ;;; throw 1 new line enddefine; I ran it a couple of times. No problem -- too simple for an error. Even for me. Suddenly, I felt two hands close around my windpipe. As everything went black, I heard, "You <****>, it took me two hours to debug that <******- ******>." Of course, being choked to death by irate C programmers is only one of the many hazards of using a dynamically typed language ...
wallace@hpdtczb.HP.COM (David Wallace) (03/20/91)
>> = David Gudeman > = Dan Bernstein >> Programs in >> dynamically typed languages are generally half to a tenth the size of >> programs in statically typed languages that do the same thing. > >I don't believe you. Give an example. It's only a single data point, but my first prototype version of ATV (the abstract timing verifier I wrote for my dissertation work) was 1600 lines of C code. At that point I changed to Lisp, and got the same functionality in less than 300 lines of Common Lisp code. I also got additional functionality for free: command scripts, the next thing I wanted to add to the program, took 0 lines of code in Lisp. (load "file") worked just fine. The 5+:1 code ratio here is certainly consistent with David's ranges. Dave W. (wallace@hpdtl.ctgsc.hp.com)
yodaiken@chelm.cs.umass.edu (victor yodaiken) (03/20/91)
In article <815@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <L-2A3G2@xds13.ferranti.com> Peter da Silva writes: > >]But if you don't know that y is complex, you don't know if "y <= 0" is >]legal or not... > >If you don't know the length of A, you don't know whether "A[i]" is >legal or not. So? You should know the length of A and you should >know the type of y. In someone elses code, if you see "y <= 0", >then you can assume that y is not complex. In you own code, if you >know that y is not complex you can write "y <= 0". There is no >ambiguity. > >]with this in mathematics is everyone has had a bunch of Fortran-style' >]default typing rules programmed into their head in college. > >No, the reason you can get away with this in math is that everyone who >has a clue knows that you can't compare magnitudes of complex numbers, >so if you are comparing magnitudes, the numbers must not be compex. >This is a trivial logical inference: > I believe that clarifying notation and type theory has been the subject of an enormous body of mathematical research over the last century. The "type" of an object is often quite unobvious. For example, when we write ax = bx -> a=b we are assuming that the "type" of a,b, and x and the type of the operation has the cancellation property. If we are working within semigroup theory, this assertion is false. Similarly, it is well known that if x = y - z + z' we can get an equivalent assertion by rewriting to x = y + -1(z - z') but if we apply this rule to the equation x = 1 -1 +1 -1 +1 ... (infinite), we can prove that 1=0 --- the type of the infinite sequence 1-1 +1 .... is not "number", so the rules we have invoked cannot be applied. I'm not at all sure that type determination is always something that one can entrust to a compiler.
peter@ficc.ferranti.com (Peter da Silva) (03/20/91)
In article <814@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > Xlisp is far from a toy, it is > more powerful than C (including C's standard libraries). For some definition of "powerful", perhaps. In my definition, which comes down to "how well does it cover the problems I want to solve" it's a toy. It doesn't even allow you to link to arbitrary libraries, for heavens' sake. > It is just a "toy" compared to other Lisp's. Like lisp 1.5? -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (03/20/91)
In article <815@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > In article <L-2A3G2@xds13.ferranti.com> Peter da Silva writes: > ]But if you don't know that y is complex, you don't know if "y <= 0" is > ]legal or not... > If you don't know the length of A, you don't know whether "A[i]" is > legal or not. So? You should know the length of A and you should > know the type of y. In someone elses code, if you see "y <= 0", > then you can assume that y is not complex. In you own code, if you > know that y is not complex you can write "y <= 0". There is no > ambiguity. This is fine if the code is: Known to be correct and debugged, and/or Your code, and You wrote it recently, or You just finished tracing it all and thus know it intimately. For maintainable code you need to document all these things. Now you can either type in declaractions, or you can type in comments that are the equivalent, but the compiler can no longer use this information to help you support the software. > This is a trivial logical inference: > (1) y is complex implies "y <= 0" is not meaningful > (2) "y <= 0" is meaningful > therefore > (3) y is not complex > There is nothing sloppy or ambiguous about this reasoning. Sure, it's got an unstated and unsupported assumption: that the code is correct. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/20/91)
David Gudeman writes: >In article <28149@dime.cs.umass.edu> victor yodaiken writes: >] >]I'm not at all sure that type determination is always something that one >]can entrust to a compiler. > >I wasn't advocating that. In most dynamically typed languages the >compiler has no idea what the type of anything is. The programmer >knows the types and, where it isn't obvious, he should document the >types. There is no reason (other than efficiency) that he should be >required to specify the type to the compiler. On the other hand, I was. At least for those cases where it's clear cut. I know that it's usually clear cut for the code I write. Generally it's pretty clear what I'm doing, at least in the sense of what type something should be. But I'm allowed to stick in declarations for the cases where the compiler's type inference gets hopelessly lost, and there's a lot of information it just ignores (*sigh*). I've been working on collecting ideas and information for the last two or three years... lots of it goes into coding style, but eventually I hope to make a compiler which lives up to what I think's possible. Since I'm not working on it for real, yet, I just do things like annoy you guys with what's possible but poorly implemented... (And I have yet to come up with very elegant ways of representing this sort of information... I'm afraid that if I were to start working on it now it'd be one god-awful huge compiler.) The confessions of a wanna-be 8-) (It would make my job easier, though, if someone ELSE were to do this...) Raul Rockwell
gudeman@cs.arizona.edu (David Gudeman) (03/20/91)
In article <28149@dime.cs.umass.edu> victor yodaiken writes:
]
]I'm not at all sure that type determination is always something that one
]can entrust to a compiler.
I wasn't advocating that. In most dynamically typed languages the
compiler has no idea what the type of anything is. The programmer
knows the types and, where it isn't obvious, he should document the
types. There is no reason (other than efficiency) that he should be
required to specify the type to the compiler.
The same applies to math. Mathematical notation should be such that
the types of variables are clear. But there is no reason why the
notation should include formal "declarations" when there are other
easy ways for a human to tell what the types are. Mathematics is
written for humans to read, not computers. I claim that programming
languages should also --as much as possible-- be designed for humans
to read, not computers.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/20/91)
In article <9106@castle.ed.ac.uk>, aipdc@castle.ed.ac.uk (Paul Crowley) writes: > I'm going to split languages in two by type extensibility. In C and ML, > you can explicitly make up a new type from old types: a widget is made > up of two foos and a bar. In Logo, there are only three types: words, > numbers, and lists. If you want to define an imaginary number, you > could use a list of two integers. If you want to define a UNIX-style > time as a list of two integers [secs, usecs], you can do that too. If > you accidentally feed an imaginary number to a function that wants a > date, the language won't say a word. Prolog behaves this way too. Just an observation here: any Prolog programmer who chooses such representations should be kicked in the backside until he or she does it *right*. A better way to represent complex numbers would be as "tagged pairs" complex(RealPart, ImaginaryPart) and a better way to represent times would be time(Seconds, MicroSeconds) These cannot be confused with each other. They also use 3/4 of the memory that a two-element list would use. In fact, with the DEC-10 Prolog type checker, you would declare :- type complex --> complex(number,number). :- type time --> time(integer,integer). and have all the reliability benefits of static type checking (but not the efficiency benefits). What's more, there isn't one teeny tiny thing in C (or, for that matter, ML) which *makes* you use separate types. In C struct num2 { double x[2]; }; struct num2 i = {0.0, 1.0}; /* a complex number */ struct num2 t = {1.2, 0.0}; /* a time */ When you think of the number of conceptually distinct types which C programmers overlay onto 'int', you realise that "static typing" *may* fail to buy you much reliability. Note the way that C, most Pascals, and many Fortrans overlay the notion "bit vector" onto integers. One great *reliability* advantage of languages like Lisp is that there is no equivalent of casts (or for PL/I fans, no UNSPEC). There _is_ a typing system and you _can't_ defeat it. -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/20/91)
In article <1991Mar15.153342.10670@rice.edu>, wright@datura.rice.edu (Andrew Wright) writes: > I would like to see a concise summary of the claimed advantages of > dynamic typing. The claimed advantages of static typing are numerous > and often argued (verification, efficiency, documentation, ...) but > the advantages of dynamic typing are less often discussed. Read "Object-Oriented Programming, an evolutionary approach" by Brad J. Cox, Addison-Wesley, ISBN 0-201-10393-1. If I can summarise his argument adequately in two sentences (and I really don't think I can do justice to it): - The fewer irrelevant constraints a software component includes in its interface, the easier it is to re-use that component or to continue to use it in an evolving application. - Latent typing is an effective way of removing some irrelevant constraints from an interface. This argument also supports Ada-style generics and ML-style polymorpism. Cox also discusses Ada generics. The static/dynamic typing issue is just one instance of the general early-binding/late-binding issue. The argument for dynamic typing can be put in a nutshell: "don't put anything in writing if you're going to regret it later". -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
gudeman@cs.arizona.edu (David Gudeman) (03/20/91)
In article <3352:Mar1803:04:5491@kramden.acf.nyu.edu> Dan Bernstein writes: ]In article <693@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]> Programs in ]> dynamically typed languages are generally half to a tenth the size of ]> programs in statically typed languages that do the same thing. ] ]I don't believe you. Give an example. One example would convince you? Say, I've got this real estate I'm trying to unload... No, I'm not willing to go to the work to dig up (or generate) examples. Sorry. I've seen examples, and those numbers are thrown around quite a lot, and they agree with my experience, but I'm not willing to work that hard just to win an argument (I may like to argue but I don't really care if I win or not...). You can probably find some examples of your own though. Try comparing the sizes of various GNU Emacs elisp packages with C programs that do the same thing. There are also articles scattered around the programming languages literature that make these sorts of comparisons. You will probably have to look back 10 years or more, since these days the advocates of dynamic typing tend to feel that the size advantage is so obvious it doesn't have to be proven any more. Now they are trying to prove it can be made as efficient as static typing (and I don't think they will succeed). -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
cs450a03@uc780.umd.edu (03/20/91)
Steve Knight writes: > >In response to a claim (or observation) made by David Gudeman that >"dynamically typed languages are generally half to a tenth the size >of programs in statically typed languages" Dan Bernstein reasonably >writes back. > >] I don't believe you. Give an example. > >Of course, this gets us into the foolish realms of anecdotes and virility >tests. It's somewhat worse, IMHO, in that there is no such thing as a true example. A true example would be a drop-in replacement for another program, maintaining its quirks and 'optimizations' as though they were mandated by <<insert your favorite authority here>>. It is also false (as Peter da Silva has indirectly pointed out) to place all the credit for compactness on dynamic typing... some compactness is from dynamic linking (or its equivalent), some is because startup code isn't needed, some might be because the program is kept in a higher-level form (e.g. threaded code). And some benefits come from incidental features (it being very easy for example, to implement overlays on a system which doesn't have them in the os, if you can manipulate 'object code' with your language primitives, and call such routines with low overhead). Perhaps what we've been arguing is not so much for dynamic typing, but the inclusion of additional types of high utility? But what if that additional type is an array whose bounds are maintained at run-time? What if there are a large number of primitives to support manipulations of the list, and so on? Basically, dynamic-typing (the way I've been thinking about it) applies to languages which allow more structure that if/then/else and do/while. Things like data-selection operations and iteration over some sequence of values (where it DOESN'T MATTER what the data is, you just want to select it, and so on). I'm talking about making these first-class functions in the language, not in the sense of 'this is an ad-hoc feature which can be used to speed up performance on platform X', but 'this is a language feature, intended to replace lower-level operations'. And to any argument that 'This can be done in C', I respond, it can be done in assembly language too. (And to any who say "that's not portable", I add that it can even be done in 8086 assembly). In so-called statically typed languages, you are forced to think and program in a low-level fashion. Dynamically typed languages (If I say 'array'-oriented or 'list'-oriented, do I exclude Icon or ML?) tend to be higher-level languages. It doesn't matter how fast your solution is if it doesn't run. Raul Rockwell
cs450a03@uc780.umd.edu (03/20/91)
Peter da Silva writes: >In article <815@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: [[ examples of type-checking, with slant towards doing it dynamically ]] >This is fine if the code is: > > Known to be correct and debugged, >and/or Your code, >and You wrote it recently, >or You just finished tracing it all and thus know it intimately. Hmm... I spend a lot of time debugging and upgrading old code. There is a lot of such code around. Some of it I just replace (like when there's a better algorithm), some of it I patch. Lots of it had bugs that showed up under obscure circumstances--things in C that would involve type-casting, mallocs, unions, etc. (and segvs and either mysterious creeping bugs or core-dumps.) Oddly enough, the code is still useful, even the sloppily written stuff. Oddly enough, I didn't write it. Oddly enough, I rarely have to trace it. Even odder, I maintain this code but still manage to spend around half (often more) my time on development. Odder still, usually the only part of the comments I find useful are the ones that identify the purpose of the function, or perhaps the purpose of a variable. I will admit that it took me a few months to get 'up to speed', and that I'm still learning things, but I disagree quite emphatically with Peter's "must" list. Raul Rockwell
yodaiken@chelm.cs.umass.edu (victor yodaiken) (03/20/91)
In article <878@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <28149@dime.cs.umass.edu> victor yodaiken writes: >] >]I'm not at all sure that type determination is always something that one >]can entrust to a compiler. > >I wasn't advocating that. In most dynamically typed languages the >compiler has no idea what the type of anything is. The programmer >knows the types and, where it isn't obvious, he should document the >types. There is no reason (other than efficiency) that he should be >required to specify the type to the compiler. > >The same applies to math. Mathematical notation should be such that >the types of variables are clear. But there is no reason why the >notation should include formal "declarations" when there are other >easy ways for a human to tell what the types are. Mathematics is Mathematical literature is full of statements of the form "let X be a finite set", let $f: S x Y -> Z$", "k ranges over the naturals", etc. etc., these are type declarations. For example, here's the Serge Lang in "algebra" (1984, Addison-Wesely) The collection of all morphisms in a category A will bedenoted by AR(A) ("arrows of A"). We shall sometimes use the symbols "f \in A(A)" to mean that f is a morphism of A ... How else are we going to make the types of the variables "clear", except by type definitions of this kind? I don't see how one can avoid declarations in either programming or math, except when the domain is very simple or very stylized. Here's an example. Suppose we define an encoding of sequences into integers with a map element(i,j)$ that picks out the ith element of the sequence encoded in j. When I write "if i=j then P" there is now an ambiguity, do I mean to compare i and j as integers, or as sequences? Since the same sequence may be encoded by distinct integers the choice is significant. In a math paper, one might dispell the ambiguity by using "u" and "v" as sequence variables, while reservng i and j for integers. So "if u = v then P" could be interpreted without ambiguity as calling for sequence comparison. But, we can only disambiguate if we have declared the types of "u" and"v". >written for humans to read, not computers. I claim that programming >languages should also --as much as possible-- be designed for humans >to read, not computers. How can one disagree with this sentiment?
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/20/91)
In article <2400035@otter.hpl.hp.com> sfk@otter.hpl.hp.com (Steve Knight) writes: > This program read a file of syllables and printed out "passwords" composed > from three random syllables. sed 's/$/XXXXXXXXXX/' | travesty -s -o10 | ... Sorry, couldn't resist. > define program(); ;;; build a list of syllables > lvars syllables = 'syllables'.discin.incharline.pdtolist; > repeat 3 times > syllables.oneof.pr ;;; print a syllable 3 times > endrepeat; > nl( 1 ); ;;; throw 1 new line > enddefine; Fine, this is sort of example I was looking for. I claim that the conciseness of this program comes from the libraries available, not from the dynamic typing. #include <stdio.h> #include "sop.h" #include "strinf.h" main() { SOP(strinf) *syl; strinf *s; int i; syl = SOPempty(strinf); while (s = strinfgets(stdin)) { strinfchop(s); SOPadd(syl,s,strinf); } for (i = 0;i < 3;++i) puts(strinftos(SOPrandpick(syl,strinf))); putchar('\n'); } (Yes, I admit I stole the name and concept of strinfchop from Perl.) Here strinf is a library for handling arbitrary-length strings, and sop is a generic set-of-pointers library. Surely you agree that, syntax aside, the C and Pop11 versions work the same way to accomplish the same results. The difference? I get better type checking and almost certainly better efficiency. For longer programs this means better turnaround time. ---Dan
peter@ficc.ferranti.com (Peter da Silva) (03/20/91)
In article <2400035@otter.hpl.hp.com> sfk@otter.hpl.hp.com (Steve Knight) writes: > Of course, being choked to death by irate C programmers is only one of the > many hazards of using a dynamically typed language ... Looks to me like this is more a factor of the richness of the runtime library, rather than the fact that it's dynamically typed. Of course, dynamically typed languages often have a rich subroutine library, but that's not always true. Consider Lisp 1.5, or CScheme. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
anw@maths.nott.ac.uk (Dr A. N. Walker) (03/21/91)
In article <731@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <1991Mar14.151707.11686@maths.nott.ac.uk> Dr A. N. Walker writes: [I assumed that "Algol", in DG's bracketing of Algol and Fortran as low-level languages, meant Algol 60] >No, I was refering to the earliest Algol tradition. Have you actually *read* the Algol 58 (or IAL) report? The Algol tradition is of a patrician disregard for efficiency, which argues against your assertions that various features were put in to make code generation easier. As late as Algol 68, the arguments are mostly about whether things can, *in principle*, be compiled; it was assumed, as a matter of faith, that compiler technology would eventually catch up. [re Fortran] >I said "static typing", not "type declarations". Early FORTRAN had >static typing but some of the declarations were implicit. Just so. This saves the "busy work" to which you objected; nevertheless, reputable authors recommend that Fortran users should put the busy work back in. >You are just arguing for array bounds checking -- arguably a form of >dynamic typing. Semantics. It's not what most people think of as dynamic typing, and it exists in Algol N (for all N!), Pascal, C, Fortran, etc., which you surely don't think of as dynamically typed languages. [Static typing has two] >]>consequences: (1) you have to limit the forms of expressions to some >]>set for which you know a type-checking decision procedure, and (2) you >]>have to acquire type information somewhere. > >] Ie, (1) you have to know what your expression is intended to do, >]and (2) you have to use variables in a disciplined way. > >Your (1) is meaningless. Of course you have to know what an >expression is intended to do in any language. This has nothing to do >with when type checking is done. If *I* can look at (say) "a+b" and decide whether the operation is defined, and what types the operands might have, and what the consequences might be for the rest of the program, so can the compiler. If you intend to make use of the *freedom* that dynamic typing can give you [and I agree that it is sometimes, even often, useful], then it follows that you *can't* know what your expression might do (except in the very boring sense that a particular language might define otherwise undefined operations to deliver 0, or some such). > Your (2) is a subjective judgement [...] Certainly. On the other hand, if writing out each identifier that you use just once extra, with [usually] one word and a semicolon, is a significant load in a serious production program, there's something wrong somewhere. Of course there are times when it's a nuisance; there are times when *any* form of documentation or commenting is a nuisance. >] There is a place for dynamic typing (I enjoy writing shell >]scripts!), and a case for rapid prototyping, [...] > >In the first place, dynamic typing is useful for full-scale working >programs, not just prototypes. I did distinguish the two! -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
dc@sci.UUCP (D. C. Sessions) (03/21/91)
In article <626@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: # In article <1991Mar13.163629.12630@engage.enet.dec.com> grier@marx.enet.dec.com writes: [Deleted for brevity -- DCS] # ]... I just wouldn't do my banking or trust my life to software which # ]relies on extensive testing rather than some level of ensurred correctness. # # You must be joking. Static type checking doesn't give any reasonable # level of assurance at all -- it is never the case that simply because # program compiles without errors, there is reason to believe that it # has some level of reliability. Testing is the _only_ known way to # give any assurance at all. And a given amount of testing generally # provides more assurance for a language with dynamic typing than it # would for a language with static typing. (Because programs in # dynamically typed languages are usually much smaller and have fewer # paths to test.) [More deletions -- DCS] # David Gudeman Let's try this theory on a real-life test case: Once upon a time, there was an engineering team which got stuck maintaining a (big) mess of spaghetti. This spaghetti contained records -- *lots* of records -- which got passed around all over the place. Most of the record types (Type_A through Type_M) were of the form: <header> <foo> <stuff> But record type Type_N was of the form: <header> <bar> <more stuff> (And by the way, <foo> and <bar> were both integers.) Now, since this was in the bad old days before function prototypes, all of the functions which expected a Type_A would quite cheerfully accept a Type_N instead. This happy circumstance was widely exploited to allow common handling of the header record, which contained information controlling the routing of the record between concurrent threads. One day, a problem showed up. On examination, it turned out that some values of <foo> dictated an alternate routing, so one of the message-routing functions was modified to handle the situation. What everyone missed was the fact that the routing function sometimes handled Type_N messages. Of course, the results of interpreting <bar> as if it were <foo> could be amusing in the extreme. Especially so since the consequences usually didn't show up until the record had been passed along to another concurrent thread. Testing didn't discover the gotcha since the anomalous value never turned up in <bar>. At least, it didn't turn up until a certain major corporation with a three-letter name revised its communications protocol. Oops. *Big* oops. Banks. Airlines. Can you say "panic debug"? It didn't take long to find the Record from Mars; tracing back to where it came from was a bit slower. Lots slower. Especially since working backwards just showed the mutant being handled correctly, right back to the point where Type_D records shouldn't come from. The upshot of this little affair was the conversion of an entire shop full of C hackers into Modula-2 fanatics, purely because they *never* wanted to give up intermodule type-safety again. So: for the purposes of the current discussion, how do our ideal dynamically-typed languages ensure that a similar little misunderstanding doesn't happen again? Of course, sufficient human discipline would avoid the problem (by doing a manual static type-check?) but this is one of those little things that some programmers have come to expect computers to do for them. -- | The above opinions may not be original, but they are mine and mine alone. | | "While it may not be for you to complete the task, | | neither are you free to refrain from it." | +-=-=- (I wish this _was_ original!) D. C. Sessions -=-=-+
peter@ficc.ferranti.com (Peter da Silva) (03/21/91)
In article <20MAR91.08580313@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > Peter da Silva writes: > >This is fine if the code is: > > Known to be correct and debugged, > >and/or Your code, > >and You wrote it recently, > >or You just finished tracing it all and thus know it intimately. > Hmm... I spend a lot of time debugging and upgrading old code. [etc...] You're over-generalising my response. I am talking about the particular case where you know what the "type" of a value is because of the immediate context assuming that the usage is valid. I don't: I have to look at a declaration or look at more than just the immediate context. > Oddly enough, I rarely have to trace it. I didn't intend imply that you have to. What I mean here is that *unless you have* traced it you can't look at a random piece of code and know the types of all the objects being dealt with. The ones you're familiar with, yes. [the only useful comments are] > the ones that identify the purpose of the function, or perhaps the > purpose of a variable. That is, type declarations. Hmmmm. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
gudeman@cs.arizona.edu (David Gudeman) (03/21/91)
In article <28190@dime.cs.umass.edu> victor yodaiken writes:
]Mathematical literature is full of statements of the form
]"let X be a finite set", let $f: S x Y -> Z$", "k ranges over
]the naturals", etc. etc., these are type declarations.
Come on, people. I didn't say that you never see anything like
declarations in mathematics. All I said is that you can often do
without them. The same is true of dynamically typed languages:
sometimes you have to check the types of variables, but often you can
do without it. And in either case, "doing without it" is neither
sloppy nor ambiguous. If it is ambiguous then you can't do without
it in the first place; and "sloppy" is in the eye of the beholder.
] The collection of all morphisms in a category A will
] bedenoted by AR(A) ("arrows of A"). We shall sometimes use the
] symbols "f \in A(A)" to mean that f is a morphism of A ...
That isn't a declaration, it's a comment. In other words, no formal
notation is being used to describe the type, an English description is
being given. If you want something like a declaration in math, try
f : A -> B
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
yodaiken@chelm.cs.umass.edu (victor yodaiken) (03/21/91)
In article <922@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > [A quote from Lang] >] The collection of all morphisms in a category A will >] bedenoted by AR(A) ("arrows of A"). We shall sometimes use the >] symbols "f \in A(A)" to mean that f is a morphism of A ... > >That isn't a declaration, it's a comment. In other words, no formal >notation is being used to describe the type, an English description is >being given. If you want something like a declaration in math, try > > f : A -> B This I don't understand at all. Here are two "comments: comment A: "In the following we use the symbols a,b to denote elements of a sequence and u and v to denote sequences" comment B: "In the following we use the symbols u,v to denote elements of a sequence and a and b to denote sequences" If I write "Comment A" and f(null)= 0, f(av) = h(g(a), f(v)) I've defined f as a recursive map on sequences But if I write "Comment B" and f(null)= 0, f(av) = h(g(a), f(v)) I've defined a quite different function. If I precede either with the comment "We use juxtaposition to indicate iterated multiplication, so that x<y1,.... yn> = <xy1,... , xyn>" I could change the meaning of either statment again by refuting our expectation that av indicates concatenation. So all these "comments" are really "type declarations" not just comments that can be stripped from the text without altering the semantics. Note that I have not had to state what elements can be placed in a sequence, so both A and B declare only a structural property If you mean to argue that type declarations in mathematical notation are generally more concise and more to the point than those offered by Fortran or C, I'll agree. But it seems as if you are arguing a larger point.
kers@hplb.hpl.hp.com (Chris Dollin) (03/21/91)
Dan writes (following up to Steve's message): > This program read a file of syllables and printed out "passwords" composed > from three random syllables. sed 's/$/XXXXXXXXXX/' | travesty -s -o10 | ... Sorry, couldn't resist. > define program(); ;;; build a list of syllables > lvars syllables = 'syllables'.discin.incharline.pdtolist; > repeat 3 times > syllables.oneof.pr ;;; print a syllable 3 times > endrepeat; > nl( 1 ); ;;; throw 1 new line > enddefine; Fine, this is sort of example I was looking for. I claim that the conciseness of this program comes from the libraries available, not from the dynamic typing. #include <stdio.h> #include "sop.h" #include "strinf.h" main() { SOP(strinf) *syl; strinf *s; int i; syl = SOPempty(strinf); while (s = strinfgets(stdin)) { strinfchop(s); SOPadd(syl,s,strinf); } for (i = 0;i < 3;++i) puts(strinftos(SOPrandpick(syl,strinf))); putchar('\n'); } (Yes, I admit I stole the name and concept of strinfchop from Perl.) Here strinf is a library for handling arbitrary-length strings, and sop is a generic set-of-pointers library. Surely you agree that, syntax aside, the C and Pop11 versions work the same way to accomplish the same results. The difference? I get better type checking and almost certainly better efficiency. For longer programs this means better turnaround time. Since neither Steve nor Dan has chosen to enlighten us as to the meanings of the identifiers used, how are we supposed to know that they are ``the same''? (Close watchers ill not that I do in fact know what the Pop routines are, but that's not the point). I do note that Dan's example had to be all squashed up to fit in a similar amount of space; laid out in a similar style it would take another five or so lines. It's not clear that Dan gets ``better'' type-checking: Steve's code works if -syllables- is a list or a vector, for example. Dan certainly gets better efficiency: -oneof- is slow on lists (because -length- and indexing are slow on lists). Given Steve's propensity for speed-hacking, I'm surprised he didn't write the definition of -syllables- as lvars syllables = {% 'syllables'.discin.incharline.pdtolist.dl %}; to generate a vector rather than a list. Still, if we're only going round three times, it hardly matters, does it? [Perhaps Steve should add -pdtovec- to our local library.] Maybe we should stop muttering about static vs dynamic typing and instead look to the *real* issue here: how would one capture the advantages of the dynamically typed systems that David is advocating but still be able to typecheck at compile-time? It's pretty clear (to me, at least) that some notion of sub-typing would be required, but I suspect that means run-time checks are needed after all. [Note for object enthusiasts: classes are NOT types.] ML doesn't cut it as it stands. Perhaps David should show us some examples where he thinks dynamic types are ``essential'' and we should attempt to devise type systems that capture that information at compile-time in a reasonablly checkable way. Perhaps Dan should present examples where static typing is all that's required - or, contrariwise, where even he would like more looseness in the type system. And please bear in mind that C is hardly a good example of a statically typed system.... -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/21/91)
In article <1991Mar13.010946.4536@engage.enet.dec.com>, grier@marx.enet.dec.com writes: > I also claim agnostance(is that a real word?) on the utility of dynamic > typing. Other than BASIC, I can't say I've ever used it to build > any real software... It's news to me that BASIC has dynamic typing. Surely a string variable X$ can't have numbers or arrays as values, an integer variable X% can't have strings or arrays as values, a string array variable B$() can't be a string or a number, a function FNA% must be an integer function, and so on. In short, surely BASIC uses static typing? -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
db@cs.ed.ac.uk (Dave Berry) (03/21/91)
In article <2400034@otter.hpl.hp.com> sfk@otter.hpl.hp.com (Steve Knight) writes: >As you can see, modern programming languages that support polymorphic >type-checking and type-inference give you all the security and performance >enhancements of a strongly typed language and all the flexibility and >convenience of a dynamically typed language. No-one claimed that ML gives you all the flexibility of dynamic typing. Someone pointed out that you don't need to declare types in a strongly typed language, which ML shows is true. ML also shows that you can have incremental compilation and interactive environments with strongly typed languages. It's a pity that so many people seem unable to separate one feature of a language (or set of languages) from other features of those langauges. Some of Steve's examples show the sort of thing that can't be done in a (purely) strongly typed language. For example, >- val foo = [false, 0]; >Error: operator and operand don't agree (tycon mismatch) There's no way that you can know the type of the head of a list containing both integer and booleans without either: 1. Dynamic typing. 2. Tagging each element. A subtype hierarchy (of some sort or other) would let you specify a general type for the elements in the list, and dynamic binding would let you call type-specific functions on each element. This is the sort of thing done in Eiffel and C++. It still doesn't give you the actual type of each element. There are schemes for adding dynamic types to strongly typed languages that would let you do things like this. They preserve type-safety by requiring explict coercions from the type "dynamic" to the desired type, which only succeeds if the object really is of the desired type. (It's likely that one of these will appear in ML at some point.) So it seems that designers of strongly-typed languages are looking at facilities to use dynamic types when they are needed, and designers of dynamically-typed languages are looking at facilities to have type-checking when needed. Perhaps we should stop arguing and start co-operating. >It is not possible to write a polymorphic hash-function. I don't see how it's possible to write a polymorphic hash-function in any language unless you know all the possible types or type-representations that the language will ever have, at the time when you write the function. Is this necessarily the case with dynamically-typed lanaguges? How could you write a polymorphic hash-function in SmallTalk? Redefining the function in each sub-class is not acceptable - you can make the equivalent definition in ML (you don't have dynamic binding, but that wasn't Steve's point). Steve's other comments about ML aren't relevant top the subject, so I'll comment on them in a different message. -- Dave Berry, LFCS, Edinburgh Uni. db%lfcs.ed.ac.uk@nsfnet-relay.ac.uk George Bush: America's Chamberlain.
sfk@otter.hpl.hp.com (Steve Knight) (03/22/91)
Chris writes: > Dan certainly gets better efficiency: -oneof- is slow on lists (because > -length- and indexing are slow on lists). Given Steve's propensity for > speed-hacking, I'm surprised he didn't write the definition of -syllables- as > lvars syllables = {% 'syllables'.discin.incharline.pdtolist.dl %}; > to generate a vector rather than a list. No fair, Chris :-) It was only an anecdote ... Still if you insist[*]. define pdtovector( p ); lvars p; {% apprepeater( p, identfn ) %} enddefine; Footnote: Besides, you know I'd have written it like this for speed define program(); lvars n = #| apprepeater( '/tmp/syllables'.discinline, identfn ) |#; repeat 3 times n.random.subscr_stack.pr endrepeat; nl( 1 ); enddefine;
cs450a03@uc780.umd.edu (03/22/91)
Peter da Silva's comments are > and >> > Mine are >> >> >This is fine if the code is: Known to be correct and >> >debugged, and/or Your code, and You wrote it recently, >> >or You just finished tracing it all and thus know it intimately. >> Hmm... I spend a lot of time debugging and upgrading old code. [etc...] >You're over-generalising my response. I am talking about the particular >case where you know what the "type" of a value is because of the immediate >context assuming that the usage is valid. I don't: I have to look at a >declaration or look at more than just the immediate context. Gack! I think I know what you're saying. I think that the types that I deal with are much simpler than the types you deal with. Or maybe I should say that each variable has its own unique type? Essentially, I deal with: character, integer, bit, file, function, and homogenous collections of each, there are also rare hetrogenous collections, and not-so-rare relationships between data of different type in different variables. Generally, each assignment to a variable is unique. (I try not to re-assign, and when I do, I try and make sure re-executing that section of code would not cause a problem). Exception made for loop counters, but not for other assignments made within the loop. So I suppose you could say I'm writing using a static typing style of coding. But if I had to have a separate declaration for each assignment, well, that would increase my code size by 50-70% right off the top (probably more, if I constructed C-like structs for each assignment I make). Not to mention it would probably triple the number of 'library routines' I'd need to deal with. All this for what? To make sure I'm protected from 'making mistakes'? Or, maybe to make my code "more efficient". Or, is someone going to try and claim that my code would become "more understandable"? >> Oddly enough, I rarely have to trace it. >I didn't intend imply that you have to. What I mean here is that >*unless you have* traced it you can't look at a random piece of code >and know the types of all the objects being dealt with. The ones >you're familiar with, yes. If it's being used with addition, it's probably gonna be a number. If it's being used with string search, it's probably a string. If it's being used with logical and it's probably a boolean. If that doesn't apply to your language, then maybe typing isn't strong enough in your language. >[the only useful comments are] >> the ones that identify the purpose of the function, or perhaps the >> purpose of a variable. >That is, type declarations. Hmmmm. Yeah, type declarations. Neither dynamic type declarations nor static type declarations, but descriptive type declarations. A feature supported in every language I've ever heard of (even machine language). Raul Rockwell
cs450a03@uc780.umd.edu (03/22/91)
Dr. A. N. Walker writes: > If *I* can look at (say) "a+b" and decide whether the operation >is defined, and what types the operands might have, and what the >consequences might be for the rest of the program, so can the compiler. >If you intend to make use of the *freedom* that dynamic typing can give >you [and I agree that it is sometimes, even often, useful], then it >follows that you *can't* know what your expression might do (except in >the very boring sense that a particular language might define otherwise >undefined operations to deliver 0, or some such). Er, what do you mean by "make use of the *freedom* that dynamic typing can give"? I'd think that using a primitive which has meaning for more than one type would qualify. And so should writing some operation intended to have the same effect on different types of data. And so should using a primitive which would be impossible to define if it's results had to be of some singular type. My first thought is rather weak, I know. Statically typed languages usually let you do things like check equality between objects of the same type. And a died-in-wool static-typing-hardliner would claim that it is meaningless to find out that objects of different types are not equal. Examples of the second also can be done in statically typed languages, things like sorting, searching, and selection. But even C (which some people have said we shouldn't be using as an example of a statically typed language) doesn't have a hashed search (for un-ordered items) in any of the libraries I know of. And I'd like to hear about such things in a language other than C... Who knows, I might be in an environment where I'll have to use another language. For the last one (primitives returning data of any type), I'll give two examples: (1) Typed data on file (including array bounds, which some people might claim is not type information). (For those who haven't read my earlier postings, I've been claiming that static type checking isn't strong enough). (2) An 'Eval' primitive. Before this is safe, I know, you have to be able to validate each of the symbols used (make sure they are appropriate for the problem). But how could you even provide such a service in some other language? Or is somebody going to claim that if I need the semantics of Eval, I'm supposed to write my own from ground up? Also, I know that with C you can write a meta-program which writes another program, and that often serves the same purpose. But how do you validate that the meta-program wrote the right stuff into the dynamically created program? I find these more of a pain to write and debug than they should be. I've also found other people's work along the same lines to often be a little non-portable (address errors from lex/yacc code, for instance). Finally, there are a number of things that I'd dread even trying to describe in a statically typed language (things which take functions as arguments and yield new functions as a result). Yet I claim that I can understand what the derived functions would do when applied to my code. Of course, I suppose many of these are equivalent to control structures in a static language (but control structures have their own problems... ) The world of statements is a disorderly one, with few useful mathematical properties. Structured programming can be seen as a modest effort to introduce some order into this chaotic world, but it accomplishes little in attacking the fundamental problems created by the word-at-a-time von Neumann style of programming, with its primitive use of loops, subscripts, and branching flow of control. John Backus -- 1977 Turing Award Lecture Raul Rockwell
gudeman@cs.arizona.edu (David Gudeman) (03/22/91)
In article <KERS.91Mar21130048@cdollin.hpl.hp.com> Chris Dollin writes:
]
]Perhaps David should show us some examples where he thinks dynamic types are
]``essential''
Can't think of a one. Dynamic typing is a convenience and a
productivity enhancer, but it isn't essential.
]And please bear in mind that C is hardly a good example of a statically typed
]system....
Actually, C is as pure a statically typed language as you can get, it
just isn't strongly typed. Languages that are more strongly typed
usually get that way by flavoring with a little dynamic typing. This
appears in such things as array bounds checking and ensuring that a
pointer is non-null before dereferencing it.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (03/22/91)
In article <1991Mar20.185308.8275@maths.nott.ac.uk> Dr A. N. Walker writes:
]
] Have you actually *read* the Algol 58 (or IAL) report?
I don't recall. Not in a long time if ever.
] The Algol
]tradition is of a patrician disregard for efficiency, which argues against
]your assertions that various features were put in to make code generation
]easier.
I'll bet though that what you consider a patrician disregard for
efficiency I will see as a psychotic fixation on same.
] As late as Algol 68, the arguments are mostly about whether
]things can, *in principle*, be compiled;
Yep, psychotic fixation on efficiency. They weren't arguing on
whether something could be _implemented_, they were worried about
producing machine code. Meanwhile the lisp people were happily
implementing things without worrying how fast they would run.
] Certainly. On the other hand, if writing out each identifier
]that you use just once extra, with [usually] one word and a semicolon,
No, that is not the problem with static typing. It is not uncommon to
have to declare variables in dynamically typed languages, you just
don't specify the type. When I complain about too many declarations,
I'm not talking about declaring simple variables, I'm talking about
the large detailed declarations needed to create any data structure
with even moderate complexity. I believe that these declarations are
responsible for an inordinately large percentage of the errors in
programs.
Another problem is that static type declarations usually require the
programmer to over-specify the solution. This is a problem for
initial developement, maintenance, and code re-usability. In initial
developement, it is not uncommon to get continuous requirements
changes and these are easier to deal with if fewer details have been
engraved in code. Similar comments apply to maintenance.
Dan mentions that the power of a language is related to the amount of
re-usable code written in the language, but that is an incomplete
observation. There is also the question of how much a language
encourages the writing of re-usable code. In statically typed
languages it is always more difficult to write a generic package than
a special-purpose package. The difference in difficulty varies a
little over languages, but the fact remains that if it is _any_ more
difficult to write re-usable code, less re-usable code will be
written. In dynamically typed languages it is just as easy to produce
re-usable code as not. In fact it takes some imagination to make code
not re-usable.
Ironically, those programing features that are intended to make it
more possible to write re-usable code tend to lead to more voluminuous
declarations, thereby making the error problem worse.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
peter@ficc.ferranti.com (Peter da Silva) (03/22/91)
In article <1610006@hpdtczb.HP.COM> wallace@hpdtczb.HP.COM (David Wallace) writes: > I also got additional functionality > for free: command scripts, the next thing I wanted to add to the program, > took 0 lines of code in Lisp. (load "file") worked just fine. Forth is a statically "typed" language, and has the same advantage. This has nothing to do with dynamic typing. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (03/22/91)
In article <21MAR91.23594992@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > Gack! I think I know what you're saying. I think that the types that > I deal with are much simpler than the types you deal with. Or maybe I > should say that each variable has its own unique type? I'm not even necessarily talking about any particular language. I'm an agnostic on the whole issue of which is "better". I use each tool for what it's good for, subject to the availability of good compilers. But I do find declarations a useful tool when I'm using a statically typed language with a sufficiently rich type system (neither C nor Pascal really qualifies here, for different reasons). > Generally, each assignment to a variable is unique. (I try not to > re-assign, and when I do, I try and make sure re-executing that > section of code would not cause a problem). Exception made for loop > counters, but not for other assignments made within the loop. This is an unusual coding style, in my experience. Are you actually limitin assignments, or are you hiding those assignments in call by reference? Perhaps a code fragment would help. > >[the only useful comments are] > >> the ones that identify the purpose of the function, or perhaps the > >> purpose of a variable. > >That is, type declarations. Hmmmm. > Yeah, type declarations. Neither dynamic type declarations nor static > type declarations, but descriptive type declarations. A feature > supported in every language I've ever heard of (even machine > language). But wouldn't it be nice if the language understood those declarations? -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (03/23/91)
In article <22MAR91.09242511@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > (2) An 'Eval' primitive. If you need the semantics of eval, you need an interpreted (or incrementally compiled) language. As a counterexample, Forth provides for this tool but is not a dynamically typed language in any sense of the word. Of course, any dynamically typed language has an "inner interpreter", but that's a different beast from the outer interpreter that understands the source language. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/23/91)
Peter da Silva > Me >> >> Generally, each assignment to a variable is unique. (I try not to >> re-assign, and when I do, I try and make sure re-executing that >> section of code would not cause a problem). Exception made for loop >> counters, but not for other assignments made within the loop. >This is an unusual coding style, in my experience. Are you actually >limitin assignments, or are you hiding those assignments in call by >reference? Perhaps a code fragment would help. It's not really that unusual... especially when you consider that I try but don't always succeed ;-) A typical C ferinstance would be any code where you initialize a table. Other C examples include things like |= or &= (after initializing with some neutral value). Code example, hmm.. anything I can think of is boring or long or too complicated to remember off the top of my head. meta-sample code (this code would really run, given appropriate functions, and the assignments are pure (each function mallocs a new array or whatever, which gets freed when its ref count goes to zero)). text =. F_READ: file text =. (HANDLE_BACKSLASH: '.\r..\0.') TEXTREPL: text text F_REPLACE: file Ok, normally I do a lot more than just kill garbage characters (carriage returns and ascii nulls in this example) the point here is that you could execute any of these lines as many times as you like. (Just try and execute them in order, please.) Is that what you mean by an example? (And I hope you don't mind if I don't provide "real code" -- I have the pleasure and misfortune getting paid to write in APL. Pleasure because it's so easy, misfortune because it's so hard to get code fragments through your typical text editor, or news feed.) Incidentally, the trailing : means that the name is a true constant (can't be erased or re-assigned, though localization has normal affects). Not a feature I get to use at work (well, not in any consistent fashion), but one I'd like. (The language is J, which I'd prefer to use over APL once a sufficiently fast version exists.) [refering to descriptive comments as type declarations] >But wouldn't it be nice if the language understood those declarations? Then they'd be code. Raul Rockwell
cs450a03@uc780.umd.edu (03/23/91)
>If you need the semantics of eval, you need an interpreted (or incrementally >compiled) language. As a counterexample, Forth provides for this tool >but is not a dynamically typed language in any sense of the word. True, but I'm not sure it's very statically typed either. Raul
throopw@sheol.UUCP (Wayne Throop) (03/24/91)
- gudeman@cs.arizona.edu (David Gudeman) -] As late as Algol 68, the arguments are mostly about whether -] things can, *in principle*, be compiled; - Yep, psychotic fixation on efficiency. They weren't arguing on - whether something could be _implemented_, they were worried about - producing machine code. It seems clear that David is misinterpreting the above statement. There is NOT a fixation on efficency evident here... the phrase "can, in principle, be compiled" means exactly what David means by "can be implemented" in this context. -- Wayne Throop ...!mcnc!dg-rtp!sheol!throopw
throopw@sheol.UUCP (Wayne Throop) (03/24/91)
- wallace@hpdtczb.HP.COM (David Wallace) >> = David Gudeman > = Dan Bernstein >> Programs in dynamically typed languages are generally half to a tenth >> the size of programs in statically typed languages that do the same thing. >I don't believe you. Give an example. - It's only a single data point, but my first prototype version of ATV (the - abstract timing verifier I wrote for my dissertation work) was 1600 lines of - C code. At that point I changed to Lisp, and got the same functionality in - less than 300 lines of Common Lisp code. As another data point (one well-studied one of a cluster of such I'm familiar with), a configuration management and automated build program was implemented in about 1000 lines of LISP code, and swelled to 5000 lines when implemented in C. The reimplementation was well studied, because there was controversy over whether any future tools should be done in LISP. In this examples, and some other similar but less thoroughly studied examples of the same controversy, there was essentially no advantage in terms of the cpu time consumption of this particular application. There was, however, a 3-to-1 reduction in memory working set size in favor of C, as well as a whopping 30-to-1 reduction in memory paged in during startup of the application in favor of C. And, of course, the C could be made available on many more of the platforms we were interested in. The studies undertaken at that time showed that the LISP system could be made more competitive in terms of its memory consumption, especially its startup thrashing, but the ultimate conclusion was that this wasn't worth the rather large investment it was estimated to take, at least not when amortized over the anticipated applications that might be implemented with the improved system. - I also got additional functionality - for free: command scripts, the next thing I wanted to add to the program, - took 0 lines of code in Lisp. This was our experience also (in reverse: that is, we lost a great deal of flexibility in our command scripts going to C). But... As Peter da Silva points out, much of this isn't due to dynamic typing. In fact, much of the benefit of the Common Lisp environment as we saw it was due to the large library of pre-canned functions that are part of the environment, and the power and generality of the syntax. Now SOME of the flexibility and size of the standard available functions can be attributed to dynamic typing, but MOST of it cannot. Note also that much of the DISadvantage of Common Lisp's memory consumption (both address space and working set) was also NOT due to dynamic typing, but rather due to run-time evaluation, so that even if the application code didn't mention (say) complex arithmetic, it couldn't be assumed that a runtime evaluation of a text string wouldn't involve it. -- Wayne Throop ...!mcnc!dg-rtp!sheol!throopw
cs450a03@uc780.umd.edu (03/24/91)
Dave Berry writes: >Perhaps we should stop arguing and start co-operating. Umm... I really appreciate the sweetness and light and everything, but I'm not totally sure I'm clear on the difference as long as information is being freely exchanged. Steve Knight: It is not possible to write a polymorphic hash-function. >I don't see how it's possible to write a polymorphic hash-function in >any language unless you know all the possible types or >type-representations that the language will ever have, at the time >when you write the function. If you're talking about a small-talkish language, you just require that the 'type' allow whatever methods you need to implement your hashing. Raul Rockwell
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/25/91)
In article <22MAR91.09242511@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: > Examples of the second also can be done in statically typed languages, > things like sorting, searching, and selection. But even C (which some > people have said we shouldn't be using as an example of a statically > typed language) doesn't have a hashed search (for un-ordered items) in > any of the libraries I know of. Log on to a UNIX System V box and type man hsearch It has been around for _years_. (See also lsearch and tsearch.) -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/25/91)
In article <8078@skye.cs.ed.ac.uk>, db@cs.ed.ac.uk (Dave Berry) writes: > >- val foo = [false, 0]; > >Error: operator and operand don't agree (tycon mismatch) > > There's no way that you can know the type of the head of a list > containing both integer and booleans without either: > > 1. Dynamic typing. > 2. Tagging each element. Alan Mycroft wrote a paper in the early 80s about adding dynamic typing to languages like ML in a fully type-safe way. (Basically, dynamically typed values are "tagged". To get at a particular value you had to use something like a conformity-case.) > >It is not possible to write a polymorphic hash-function. > > I don't see how it's possible to write a polymorphic hash-function in any > language unless you know all the possible types or type-representations > that the language will ever have, at the time when you write the function. In the case of something like Lisp, we do know all the possible type representations. There is no particular reason why ML couldn't have a built-in polymorphic "hash" function that applies to any "equality" type. Abstract data types are a problem, but if you can replace '=' for a data type you can replace 'hash' for it as well. (Lisp has a short-cut here which ML lacks; a hash function in Lisp has to approximate EQ rather than EQUAL.) -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/25/91)
In article <1610006@hpdtczb.HP.COM> wallace@hpdtczb.HP.COM (David Wallace) writes: > It's only a single data point, but my first prototype version of ATV (the > abstract timing verifier I wrote for my dissertation work) was 1600 lines of > C code. At that point I changed to Lisp, and got the same functionality in > less than 300 lines of Common Lisp code. I also got additional functionality > for free: command scripts, the next thing I wanted to add to the program, > took 0 lines of code in Lisp. (load "file") worked just fine. The 5+:1 code > ratio here is certainly consistent with David's ranges. But how many of those 1300 lines appeared anyway in the language library? Command scripts, for example, are just a function of the programming environment, and don't prove anything about dynamic typing. What was it about dynamic typing that made your code so much shorter? ---Dan
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/26/91)
In article <KERS.91Mar21130048@cdollin.hpl.hp.com> kers@hplb.hpl.hp.com (Chris Dollin) writes: > I do note that Dan's example had to be all squashed up to fit in a similar > amount of space; laid out in a similar style it would take another five or so > lines. Feh. Here are the two pieces of code, side by side, with a few changes to fit a change in the strinf library and to show the parallels: #include <stdio.h> #include "sop.h" #include "strinf.h" main() { define program(); strinf *s; int i; SOP(strinf) *syl; lvars syllables = syl = SOPempty(strinf); while (s = strinfgets(stdin)) { 'syllables'.discin.incharline.pdtolist; strinfchop(s); SOPadd(syl,s,strinf); } for (i = 0;i < 3;++i) { repeat 3 times puts(strinfs(SOPrandpick(syl,strinf))); syllables.oneof.pr } endrepeat; putchar('\n'); nl( 1 ); } enddefine; Sure, the C version is longer: it #includes library header files (to declare functions), and declares variables. But everything else is parellel. I didn't define repeat or nl, and I didn't hide discin or incharline inside a subroutine, but I'm sure we can all manage to pay attention to the real issue rather than these syntactic trivialities. The C code is no more complex than the original. End of example. > It's not clear that Dan gets ``better'' type-checking: Steve's code > works if -syllables- is a list or a vector, for example. What's your point? I do get better typechecking: the compiler tells me immediately that I haven't made any type errors. Given that it didn't take me a significant amount of time to include the declarations, what do I lose? [ speed ] Actually, if I needed this program for something, I'd be more worried about memory. I'd use one of the selection algorithms in Knuth to pick three (or however many) random syllables from the list on the fly, without having the whole thing in memory at once; then I'd output those syllables in a random order. In any case, the superior speed of the C version doesn't really matter given that it already provides superior typechecking. > Maybe we should stop muttering about static vs dynamic typing and instead look > to the *real* issue here: how would one capture the advantages of the > dynamically typed systems that David is advocating but still be able to > typecheck at compile-time? *What* advantages? If any advantages exist, they certainly didn't matter for this example. > And please bear in mind that C is hardly a good example of a statically typed > system.... It's adequate. ---Dan
john@clouseau.mitre.org (John D. Burger) (03/26/91)
db@cs.ed.ac.uk (Dave Berry) wrote: I don't see how it's possible to write a polymorphic hash-function in any language unless you know all the possible types or type-representations that the language will ever have, at the time when you write the function. and ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) replied: In the case of something like Lisp, we do know all the possible type representations. There is no particular reason why ML couldn't have a built-in polymorphic "hash" function that applies to any "equality" type. Abstract data types are a problem, but if you can replace '=' for a data type you can replace 'hash' for it as well. (Lisp has a short-cut here which ML lacks; a hash function in Lisp has to approximate EQ rather than EQUAL.) Actually, in Common Lisp, when a programmer creates a hash table she specifies the equality test she wants to use. The implementation must provide EQ, EQL, EQUAL and EQUALP hash tables, which cover the basic flavors of equality in Common Lisp. A programmer also has access to an underlying hashing function, if he wants to implement his own hashed structures. This function is SXHASH, which implements EQUAL equality. That is: (EQUAL X Y) implies (= (SXHASH X) (SXHASH Y)) However, none of these built-in predicates implement component-wise equality for user-defined datatypes, so it's not possible for two different instances of a particular datatype to (necessarily) be hashed alike, even if their components are identical. -- John Burger john@mitre.org "You ever think about .signature files? I mean, do we really need them?" - alt.andy.rooney
cs450a03@uc780.umd.edu (03/26/91)
Richard O'Keefe > Me >> >> But even C ... doesn't have a hashed search (for un-ordered items) >> in any of the libraries I know of. > >Log on to a UNIX System V box and type > man hsearch >It has been around for _years_. (See also lsearch and tsearch.) I did. Thought I was going to see a horrendous call with a table arg, function args, etc. (leaving all the work to be done elsewhere). Instead I saw something that scared me worse: A global hash table (now, THAT's useful)! I want to use hashing to speed up this little old function here, and all I have to do is make sure it's a unique instance of the solution... Maybe my nonlocality gripes are petty, but geez... Raul Rockwell
peter@ficc.ferranti.com (Peter da Silva) (03/26/91)
In article <22MAR91.20485982@uc780.umd.edu> cs450a03@uc780.umd.edu writes:
Orig-Paul> Generally, each assignment to a variable is unique. (I try not to
Orig-Paul> re-assign, and when I do, I try and make sure re-executing that
Orig-Paul> section of code would not cause a problem). Exception made for loop
Orig-Paul> counters, but not for other assignments made within the loop.
Peter>This is an unusual coding style, in my experience. Are you actually
Peter>limitin assignments, or are you hiding those assignments in call by
Peter>reference? Perhaps a code fragment would help.
Paul> It's not really that unusual... especially when you consider that I
Paul> try but don't always succeed ;-) A typical C ferinstance would be any
Paul> code where you initialize a table. Other C examples include things
Paul> like |= or &= (after initializing with some neutral value).
OK, I still don't follow what the point is. Why avoid assignments, and how do
you do things like state machines or stepping through a list?
Paul> [refering to descriptive comments as type declarations]
Peter>But wouldn't it be nice if the language understood those declarations?
Paul> Then they'd be code.
That's the point.
--
Peter da Silva. `-_-' peter@ferranti.com
+1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (03/26/91)
In article <22MAR91.22190193@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > >If you need the semantics of eval, you need an interpreted (or incrementally > >compiled) language. As a counterexample, Forth provides for this tool > >but is not a dynamically typed language in any sense of the word. > True, but I'm not sure it's very statically typed either. It's weakly statically typed. Basically, it has two types, the character and the cell. You can build more complex types with <builds-does> or whatever the latest fashionable term for that is, but it remains statically typed. Of course you can build a dynamically typed language on top of it with appropriate definitions... but the basic language is weakly and statically typed. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/26/91)
Wayne Throop > (and, I presume >-] ) David Gudeman >- >-] As late as Algol 68, the arguments are mostly about whether >-] things can, *in principle*, be compiled; >- Yep, psychotic fixation on efficiency. They weren't arguing on >- whether something could be _implemented_, they were worried about >- producing machine code. >It seems clear that David is misinterpreting the above statement. There >is NOT a fixation on efficency evident here... the phrase "can, in >principle, be compiled" means exactly what David means by "can be >implemented" in this context. Well, I wouldn't call it psychotic, more of a blind spot. I posted an earlier article on J (the one about run-time syntax checking). Just to give you the creeping horrors, consider the following line of code: x=. +x 1 2 3 ,(*(x=./) 4 5 6) x 7 8 9 (x=.+) 10 Here's how it would parse: x=. +x 1 2 3 ,(*(x=./) 4 5 6) x 7 8 9 (x=.+) 10 x=. +x 1 2 3 ,(*(x=./) 4 5 6) x 7 8 9 (+) 10 x=. +x 1 2 3 ,(*(x=./) 4 5 6) x 7 8 9 + 10 x=. +x 1 2 3 ,(*(x=./) 4 5 6) + 7 8 9 + 10 x=. +x 1 2 3 ,(*(x=./) 4 5 6) + 17 18 19 x=. +x 1 2 3 ,(*(/) 4 5 6) + 17 18 19 x=. +x 1 2 3 ,(*/ 4 5 6) + 17 18 19 note: */4 5 6 <--> 4*5*6 x=. +x 1 2 3 ,(120) + 17 18 19 x=. +x 1 2 3 ,120 + 17 18 19 x=. +x 1 2 3 ,137 138 139 x=. +/ 1 2 3 ,137 138 139 x=. +/ 1 2 3 137 138 139 x=. 420 420 In this case, implementing machine code is trivial -- the whole line boils down to the constant 420. However, one could easily replace any of the objects on the above line with a variable, or a read from standard input, etc. Not that any of that would be particularly meaningful in this case. Now, the line of code I choose is particularly ill-conditioned, which makes it ideal for pointing out that things can be implemented which do not have an efficient implementation for all cases. I think that a lot of things might have been knocked out of ALGOL, not because they could not be implemented, but because they didn't map well into the framework the implementors were using. Raul Rockwell
wallace@hpdtczb.HP.COM (David Wallace) (03/26/91)
> peter@ficc.ferranti.com (Peter da Silva) >> Me >> I also got additional functionality >> for free: command scripts, the next thing I wanted to add to the program, >> took 0 lines of code in Lisp. (load "file") worked just fine. > >Forth is a statically "typed" language, and has the same advantage. This >has nothing to do with dynamic typing. Agreed. I was supplying information about the one example I have hard data on, not arguing that all the difference was due to static vs. dynamic typing. This wasn't a controlled experiment in which only one independent variable was changed, it's a historical anecdote that happens to meet the basic conditions David G. was describing (basically the same program in a dynamic vs. a statically typed language). My comment above was intended to clarify the degree to which the two programs had the same functionality: they did, in that all the code I re-wrote in Lisp was intended to replicate the existing functionality of the C version, but they also didn't, because I was able to add this new functionality to the Lisp version without writing any new code. If I had added command scripts to the C version also, we would have had two functionally equivalent comparisons: 1600:300 for the comparison without command scripts, and (1600+X):300 for the comparison with them. Obligatory caveats: this is the one example I happened to have data for. It is consistent with David Gudeman's assertion. It does not prove it, though it does add to the credibility of the assertion for me. Two potential sources of bias are: 1) This was early prototype code. As such, both versions lacked the extensive error checking and recovery typical of more bulletproof production code. I do not know what the ratio of code size in such code would be. I suspect it would still favor Lisp (some errors just don't happen in Common Lisp, such as integer overflow), but the ratio could very well be more even. (Note though that the reason for CL not having integer overflow is because CL exploits dynamic typing to give you automatic bignums as needed.) 2) I re-wrote the code in Lisp because I felt it would be easier to develop this particular program in Lisp than in C. The data suggests that I was right. The same results would not necessarily hold for applications that were more naturally oriented towards C than Lisp. In particular, there were chunks of the initial C code that were devoted to list manipulation, storage management, and input parsing, each of which became much simpler or a non-issue in Lisp. None of these are directly a result of dynamic typing, but they all tend to be at least weakly related (e.g., tagged storage supports both dynamic typing and garbage collection, so the two tend to go together; garbage collection in turn helps support many of the Lisp list manipulation functions; and polymorphic read functions (supported by dynamic typing) facilitate input parsing). In any case, David's specific assertion was about programs in dynamically typed languages (with all the baggage that goes along with them), not about the effects of dynamic typing in isolation. In summary: David Gudeman asserted that programs written in dynamically typed languages were generally half to a tenth the size of equivalent programs in statically typed languages. Dan Bernstein questioned the assertion and asked for an example. I supplied the data from the one non-trivial example I have known where a direct comparison was possible, and noted that the data was consistent with David's assertion. Dave W. (wallace@hpdtl.ctgsc.hp.com)
gudeman@cs.arizona.edu (David Gudeman) (03/26/91)
In article <1492@sheol.UUCP> Wayne Throop writes:
]
]It seems clear that David is misinterpreting the above statement. There
]is NOT a fixation on efficency evident here... the phrase "can, in
]principle, be compiled" means exactly what David means by "can be
]implemented" in this context.
It will take a great deal of evidence to convince me that the Algol
committee members were so ignorant that they actually argued over the
implementability of anything in Algol 60. There is nothing in the
language that any knowledgeable person would doubt can be implemented.
In fact, I can only think of two things that can't obviously be
compiled to machine code: nested scope and call-by-name. Everything
else in the language has a fairly obvious machine implementation.
I want to clarify that my use of the term "psychotic" was intended as
hyperbole, not a serious criticism of the Algol committee. But it is
just wrong to claim that they were trying to design a general notation
for describing algorithms without regard to implementation. If that
were true they surely would have included sets, strings, graphs,
concatenatable sequences and other useful things in the language. But
they did not because they wanted the language to be close in spirit to
computers.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (03/26/91)
I mentioned that System V C has hsearch. In article <25MAR91.21515980@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: > Instead I saw something that scared me worse: A global hash table > (now, THAT's useful)! I want to use hashing to speed up this little > old function here, and all I have to do is make sure it's a unique > instance of the solution... > Maybe my nonlocality gripes are petty, but geez... I guess I should have put a few smileys in my posting. I think the hsearch() interface in the System V library is very bad. I would try hard to fail any of my students who did that. Actually, I am of the opinion that typing *values* (dynamic typing) is a Good Idea -- not least because mistakes I make in C just can't be made in Scheme or won't go un-noticed if they _can_ be made. I also like languages where it's impossible for an uninitialised variable to go un-noticed (Dijkstra's notation -- I often design in that then code in C, Scheme, Pop-2, Pop-11, Arthur Sale's Pascal compiler for B6700s, ...). (declare (optimize safety)) -- Seen from an MVS perspective, UNIX and MS-DOS are hard to tell apart.
kers@hplb.hpl.hp.com (Chris Dollin) (03/26/91)
David responds:
In article <KERS.91Mar21130048@cdollin.hpl.hp.com> Chris Dollin writes:
]
]Perhaps David should show us some examples where he thinks dynamic types are
]``essential''
Can't think of a one. Dynamic typing is a convenience and a
productivity enhancer, but it isn't essential.
I made myself less than clear. I don't use ``essential'' in the sense of
``can't be programmed without'', because almost nothing is essential in that
sense; I meant that, without dynamic types, the expression of the code would be
more obscure, or longer, or plain inelegant.
Incidentally, David, are we arguing for dynamic typing (as in Lisp, Pop11, etc)
or simple absence of mandatory type declarations (as in ML)? It makes a
difference.
--
Regards, Kers. | "You're better off not dreaming of the things to come;
Caravan: | Dreams are always ending far too soon."
cs450a03@uc780.umd.edu (03/27/91)
Peter da Silva writes: [little peter/paul dialog about making assignments unique, ending: >OK, I still don't follow what the point is. Why avoid assignments, >and how do you do things like state machines or stepping through a >list?] Umm.. I like the Peter/Paul style, but my name starts with an 'R' (other than that, pronounced like Paul). (1) The point is not to avoid assignments. The point is to be able to see the program all at once. (Mentally, if not physically). (2) I generally don't need to make state machines. When I do, I try to represent the machine as a single data object (the machine state) which is mapped by a simple function from one state to another. Another thing is that usually, I'll want a history of the machine, so each state is stored in a separate location. In functional programming, there is a significant difference between initialization and re-assignment. (3) I generally don't need to step through lists. Generally, this is done for me automatically by primitives. Most of the time, I consider the stepping process trivia (especially when the primitive simulates parallel execution, as 1 1 0 0 and 1 0 1 0 yields: 1 0 0 0). Pure functions are much easier to validate than dirty code, for the same kind of reasons that static code is easier to validate than self-modifying code. Again, the point is not to prohibit re-defining of some value, the point is to use simpler techniques where possible. And, again, the advantages are in development and debugging times. When you're used to it, C statements like for (j=0; j<max ; j++) { list[j] = f(list[j], j); } seem pretty simple. However, they aren't much faster than new = (type *) malloc( ... ); for (j=0; j<max ; j++) { new[j] = f(list[j], j); } free(list); Why the extra hassle? Because now you can abort in the middle, if you hit an error, or whatever. Also, your new type doesn't have to be the same as the old type. (Though keeping track of types is even more overhead in a language like C). If you're in a debugging environment, you could easily change the argument array, and restart. Thus, I could write the above loop as new =. f (list ; i. # list) where i. # creates a little array of indices. Or, even simpler, I could write new =. f (list) since f has full access to all the details of its argument (type, dimensions, etc.) This is vaugely like the argc, argv convention of unix, but not so specialized for file names and option switches. Yes, there is overhead. Usually, for arrays smaller than 16 elements it is significantly smaller to go with a statically typed language. On larger data objects, the type checking and array bounds checking vanish in the noise. (The exact crossover depends on the function being used, the architecture of the machine you are on, and that sort of thing.) There is also storage overhead, but that isn't always significant. The efficiencies of scale are suprisingly similar to the benefits you get from a compiler. Except with a compiler you can get lots of speed-up by adding more code. Here, the speedup comes in simplifying the code and going with more data. In fact, since C makes it so awkward to dynamically allocate result arrays, people often go to various tree structures when it would be more efficient to store the results as a flat array. For some things, you NEED to get down to a low level, and be able to do things in a serial fashion, or just modify one little piece of a tree. Most of the time, you can provide that functionality in some primitive, and get on with solving the big problems. Enough time is spent re-inventing the wheel as it is. >Paul> [refering to descriptive comments as type declarations] >Peter>But wouldn't it be nice if the language understood those declarations? >Paul> Then they'd be code. >That's the point. Would you settle for a language which provided no special syntax for comments (i.e. where comments are just pieces of data that you throw into the program)? Raul Rockwell
gudeman@cs.arizona.edu (David Gudeman) (03/27/91)
In article <KERS.91Mar26115416@cdollin.hpl.hp.com> Chris Dollin writes: ] ] In article <KERS.91Mar21130048@cdollin.hpl.hp.com> Chris Dollin writes: ] ] ] ]Perhaps David should show us some examples where he thinks ] ]dynamic types are ``essential''... ] ]...I meant that, without dynamic types, the expression of the code would be ]more obscure, or longer, or plain inelegant. The examples of heterogenous lists posted by others are persuasive, more so than I would have expected. Other examples include the ability to define polymorphic functions easily: procedure max(a,b) return if a < b then b else a; end which is perfectly clear and works for any type that "<" works on. Statically typed languages either forbid this altogether, or require extra declarations that obscure the simple meaning. Another convenience is the ability to use different types to mean different things. procedure string_to_num(s) # returns the integer or float # represented by s. In case of an error, the return value is a # string describing the type of error. Statically typed languages either need a global variable or an exception handling mechanism to do this. And with the global variable method, the programmer can forget to check the value of the global, leading to undefined behavior. With the solution above, forgetting to check the result will eventually lead to a type-error that will probably be much easier to track down. ]Incidentally, David, are we arguing for dynamic typing (as in Lisp, ]Pop11, etc) or simple absence of mandatory type declarations (as in ]ML)? It makes a difference. ML does not have dynamic typing so I'm not arguing for that sort of scheme. It gives some of the advantages of dynamic typing, but probably not the more important ones. I'm arguing (1) dynamic typing is useful enough that it should be supported directly by programming languages, (2) dynamic typing is not "dangerous" in any sense, (3) static typing is important enough that it should be supported by programmig languages, and (4) it is not clear the the ability of static type declarations to catch errors outweighs the number of errors that occur in the declarations. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
rh@smds.UUCP (Richard Harter) (03/27/91)
In article <1493@sheol.UUCP>, throopw@sheol.UUCP (Wayne Throop) writes: > As another data point (one well-studied one of a cluster of such > I'm familiar with), a configuration management and automated build > program was implemented in about 1000 lines of LISP code, and swelled > to 5000 lines when implemented in C. The reimplementation was well > studied, because there was controversy over whether any future > tools should be done in LISP... > As Peter da Silva points out, much of this isn't due to dynamic typing. > In fact, much of the benefit of the Common Lisp environment as we saw it > was due to the large library of pre-canned functions that are part of > the environment, and the power and generality of the syntax. Now SOME > of the flexibility and size of the standard available functions can be > attributed to dynamic typing, but MOST of it cannot. Just another data point. We developed Lakota internally as a scripting language and have found rather similar results. For example, a 500 NCL C program was replaced by a 35 NCL Lakota program. An approoximate breakdown for the reasons for reduction in code size is: 15% Elimination of declarations 35% Use of intrinsic language functions 20% Dynamic list operations 20% Free form string concatenation To the point of this particular discussion -- Lakota is an untyped language. (More precisely, it has a single type, lists of strings; types, if needed, are implemented as ADT packages; numeric operations are recognized in functional context.) Some points that may be relevant: (a) One of the features that reduced code size and made for increased readibility was the elimination of the need for lots of "helper" variables, i.e. loop indices, auxiliary arrays, variables containing sizes and counts, etc. I rather suspect that the same would be true for the cited lisp program. Much of this can be attributed to intrinsic dynamic data structures. Calculation of sizes, allocation of storage, and variable "declarations" are handled by the language processor rather than by the programmer. (b) Intrinsic functions such as sorting and splitting strings into a list of fields played a major role in reducing size. There are several factors here. (1) The ability of function operators to act on and return lists, (2) consistent syntax which makes composition of operators convenient, and (3) the elimination of "set-up" operations, i.e. allocation, initialization, etc. The fundamental gain over C (even given the availibility of equivalent library routines) is in the smoothness of the interface between operations. Speed: Lakota is an intrinisically interpreted language. On the whole the execution times are 2-5 times slower than the equivalent C programs. [The intended usage is high-level administrative with compute intensive grunt work being done by 3GL subroutines and programs; ergo execution time is not a penalty factor.] Development time of Lakota vs C (or equivalent 3GL) is at least an order of magnitude faster -- the salient points are reduced code size, reduced syntax errors, elimination of the compile and link step, and the ability to execute individual routines in standalone mode. A final note. In the context of this discussion Lakota is not execeptional; smaller code size, sharply reduced development time, dynamic data structures, and functional composition are features of many of the other languages mentioned. I used Lakota as an example because I am working with it at the moment. -- Richard Harter, Software Maintenance and Development Systems, Inc. Net address: jjmhome!smds!rh Phone: 508-369-7398 US Mail: SMDS Inc., PO Box 555, Concord MA 01742 This sentence no verb. This sentence short. This signature done.
quale@saavik.cs.wisc.edu (Douglas E. Quale) (03/28/91)
>typed languages are generally half to a tenth the size of programs in >statically typed languages" Dan Bernstein reasonably writes back. > >] I don't believe you. Give an example. > In Common Lisp, (defun compose (f g) (lambda (&rest) (funcall f (apply g &rest)))) I think it looks better in Scheme, (define (compose f g) (lambda x (f (apply g x)))) These definitions use dynamic typing to obtain polymorphism. I think it would take at least two orders of magnitude (probably three) to do this in C. -- Doug Quale quale@saavik.cs.wisc.edu
peter@ficc.ferranti.com (Peter da Silva) (03/28/91)
In article <26MAR91.18541581@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > Umm.. I like the Peter/Paul style, but my name starts with an 'R' > (other than that, pronounced like Paul). Oops. > (2) I generally don't need to make state machines. When I do, I try > to represent the machine as a single data object (the machine state) > which is mapped by a simple function from one state to another. > Another thing is that usually, I'll want a history of the machine, so > each state is stored in a separate location. In functional > programming, there is a significant difference between initialization > and re-assignment. Hmmm. looks like we're dealing with radically different problem spaces. You deal with problems where it's desirable and possible to avoid information loss. I have to deal with that entropy and give up reversibility to allow unlimited runtime with limited memory storage. This is particularly important in interactive programs. > When you're used to it, C statements like > for (j=0; j<max ; j++) { list[j] = f(list[j], j); } > seem pretty simple. However, they aren't much faster than > new = (type *) malloc( ... ); > for (j=0; j<max ; j++) { new[j] = f(list[j], j); } > free(list); It may not be significantly slower, but it uses up significantly more memory. If list is large enought that can easily make the difference between being able to run the program and not. Also, I tend to work through dynamically allocated lists, or even lists that are implicitly defined. In that case "max" is unknown and potentially unbounded. while(msg = GetMsg(port)) { switch(msg->request) { ... case FOO: state = BAR; } Reply(msg); } I don't care how GetMsg is implemented, or Reply, but I do need to assume that there is no limit on the number of times this loop will be run through. This means I *have* to discard old state information. > In fact, since C makes it so awkward to dynamically allocate result > arrays, people often go to various tree structures when it would be > more efficient to store the results as a flat array. This is a limitation in the standard library: it doesn't have a "sack of data" set of routines. Other C runtime libraries do have such things. > Most of the time, you can provide that functionality in some > primitive, and get on with solving the big problems. That's true, but I don't see how it relates to this question. This is the smart way to operate in any langauge, no matter how the type rules work. > >Raul> [refering to descriptive comments as type declarations] > >Peter>But wouldn't it be nice if the language understood those declarations? > >Raul> Then they'd be code. > >That's the point. > Would you settle for a language which provided no special syntax for > comments (i.e. where comments are just pieces of data that you throw > into the program)? Regarding what? -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/28/91)
Brian Boutel writes: >This typing can be inferred at compile time, which is what is meant >by static. Typing inferred at compile time is a good thing, no matter what you call it. Raul
brian@comp.vuw.ac.nz (Brian Boutel) (03/28/91)
In article <1991Mar27.161304.17666@daffy.cs.wisc.edu>, quale@saavik.cs.wisc.edu (Douglas E. Quale) writes: |> >typed languages are generally half to a tenth the size of programs |> in |> >statically typed languages" Dan Bernstein reasonably writes back. |> > |> >] I don't believe you. Give an example. |> > |> |> In Common Lisp, |> |> (defun compose (f g) |> (lambda (&rest) (funcall f (apply g &rest)))) |> |> I think it looks better in Scheme, |> |> (define (compose f g) |> (lambda x (f (apply g x)))) |> |> These definitions use dynamic typing to obtain polymorphism. |> |> I think it would take at least two orders of magnitude (probably |> three) to |> do this in C. |> |> -- Doug Quale |> quale@saavik.cs.wisc.edu Why do you think this is dynamic typing? Definitions parallel to this exist in statically typed languages like Haskell and ML. In Haskell, write compose f g = \ x -> f (g x) This is even shorter than the lisp/scheme versions. Its type (in the normal Hindley/Milner type system) is (forall T1, T2, T3), (T1->T2) -> (T3->T1) -> (T3->T2) This is polymorphic because any types can be (consistantly) substituted for the type variables T1,T2 and T3. This typing can be inferred at compile time, which is what is meant by static. So try again. --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/28/91)
In article <1162@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > The examples of heterogenous lists posted by others are persuasive, > more so than I would have expected. Other examples include the > ability to define polymorphic functions easily: > procedure max(a,b) return if a < b then b else a; end #define max(a,b,lt) ((lt)((a),(b)) ? (b) : (a)) Or any max(a,b,lt) any a; any b; int lt(); { if (lt(a,b)) return a; else return b; } > procedure string_to_num(s) # returns the integer or float > # represented by s. In case of an error, the return value is a > # string describing the type of error. > Statically typed languages either need a global variable or an > exception handling mechanism to do this. And with the global variable > method, the programmer can forget to check the value of the global, > leading to undefined behavior. With the solution above, forgetting to > check the result will eventually lead to a type-error that will > probably be much easier to track down. But an exception handling mechanism obviously produces the best results: you need to take positive action to catch the exception, so if you forget to handle it then you're guaranteed to get an error. In contrast, ``will eventually lead to a type-error'' is not particularly comforting. Your program may be doing something entirely different in the meantime, and lots of other data may be corrupted. > (1) dynamic typing > is useful enough that it should be supported directly by programming > languages, Supported, yes; but why does the support have to be direct? Why can't it just be a set of library routines, plus the syntactic sugar necessary to make you happy? > (4) it is not clear the the ability of > static type declarations to catch errors outweighs the number of > errors that occur in the declarations. Fair enough. However, just in case the benefits of static typing *do* outweigh the errors---possibly by a lot---doesn't it seem wise to provide the support? ---Dan
augustss@cs.chalmers.se (Lennart Augustsson) (03/28/91)
In article <1991Mar27.161304.17666@daffy.cs.wisc.edu> quale@saavik.cs.wisc.edu (Douglas E. Quale) writes: >In Common Lisp, > >(defun compose (f g) > (lambda (&rest) (funcall f (apply g &rest)))) > >I think it looks better in Scheme, > >(define (compose f g) > (lambda x (f (apply g x)))) > >These definitions use dynamic typing to obtain polymorphism. > Yes, in LISP it's dynamic typing that gives you the polymorphism, but you don't have to have dynamic typing. Let's do the same definition in ML, which is statically typed: - fun compose(f,g) x = f(g x); > val compose = fn : (('a -> 'b) * ('c -> 'a)) -> ('c -> 'b) (I wrote the line starting with '-', the system responded with the the line starting with '>'.) This compose function is typed and still polymorphic. Some examples: - fun inc x = x+1; > val inc = fn : int -> int - compose(not,not) true; > true : bool - compose(inc,compose(inc,inc)) 0; > 3 : int - compose(inc,not) true; Type clash in: (compose (inc,not)) Looking for a: int I have found a: bool -- Lennart Augustsson [This signature is intentionally left blank.]
gudeman@cs.arizona.edu (David Gudeman) (03/28/91)
In article <25317:Mar2803:26:1291@kramden.acf.nyu.edu> Dan Bernstein writes: ]In article <1162@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ] ]#define max(a,b,lt) ((lt)((a),(b)) ? (b) : (a)) Not a proper function on args with side effects. Though it does demonstrate that macros can sometimes be used to simulate static polymorphism. ]any max(a,b,lt) ]any a; ]any b; ]int lt(); ]{ ] if (lt(a,b)) ] return a; ] else ] return b; ]} That is a tiny fraction of the whole solution. The whole solution requires implementation of tagged structures, and garbage collection, and requires that all of your numbers be specified with constructors of some type. That is, you can't just write for your dynamically typed value, you have to write something like integer(3). Of course, integer(3) is completely different from 3 and you can't do normal C arithmetic on it, so you have to define all the arithmetic functions in dynamic form. And your code becomes almost unreadable since everything has to be done in function call syntax. Yes, I know the syntax wouldn't be a problem in C++. Everything else still is. ]... In contrast, ]``will eventually lead to a type-error'' is not particularly comforting. ]Your program may be doing something entirely different in the meantime, ]and lots of other data may be corrupted. I should have said "will eventually lead to a type-error, almost certainly during the first few test runs", since testing with bogus input should be high on the agenda. (Although it is true that an exception handling machanism is the best solution.) ]> (1) dynamic typing ]> is useful enough that it should be supported directly by programming ]> languages, ] ]Supported, yes; but why does the support have to be direct? Why can't it ]just be a set of library routines, plus the syntactic sugar necessary to ]make you happy? First, dynamic typing is a simpler, more general, and more expressive underlying semantic model. It makes no sense to design a language under a less satisfactory model and then to patch on the better model with libraries and preprocessors. Static typing is a hack to gain efficiency on current architectures, so if anything it is static typing that should be an add-on. And type declarations are often superfluous so they should not be required in the underlying model. Second, it is not possible to tack dynamic typing on a statically typed language (using a preprocessor and a library) without paying some penalty somewhere. Dynamic typing needs direct support from the debugger, for one thing. For another, there are optimizations that the compiler can't do without understanding dynamic typing. For example if if (integer(i)) { /* a bunch of code using i */ } is understood by the compiler, then it can optimize the use of i, by generating integer arithmetic and not checking the type for each operation. But if gets translated to if (i.tag == INTEGER) { /* a bunch of code using i */ } then the compiler doesn't have any idea what's going on. Of course you could have the preprocessor do some optimization itself and provide debugging information to the compiler; but then what you are calling a "preprocessor", I'd call an analysis phase of the compiler, and what you call your "language", I'd call an intermediate implementation language. ]> (4) it is not clear the the ability of ]> static type declarations to catch errors outweighs the number of ]> errors that occur in the declarations. ] ]Fair enough. However, just in case the benefits of static typing *do* ]outweigh the errors---possibly by a lot---doesn't it seem wise to ]provide the support? Did you see number (3)? -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
nick@cs.edinburgh.ac.uk (Nick Rothwell) (03/29/91)
In article <1991Mar27.161304.17666@daffy.cs.wisc.edu>, quale@saavik.cs.wisc.edu (Douglas E. Quale) writes: > >typed languages are generally half to a tenth the size of programs in > >statically typed languages" Dan Bernstein reasonably writes back. > > > >] I don't believe you. Give an example. > > > > In Common Lisp, > > (defun compose (f g) > (lambda (&rest) (funcall f (apply g &rest)))) > > I think it looks better in Scheme, > > (define (compose f g) > (lambda x (f (apply g x)))) > > These definitions use dynamic typing to obtain polymorphism. ML (statically typed): fun compose (f, g) x = f(g x) This definition uses polymorphism to obtain polymorphism. BTW, I undercut you by a few characters (and several brackets). >I think it would take at least two orders of magnitude (probably three) to >do this in C. C is trashy, we know this already. Nick. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "I see what you see: Nurse Bibs on a rubber horse."
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (03/29/91)
In article <1211@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > In article <25317:Mar2803:26:1291@kramden.acf.nyu.edu> Dan Bernstein writes: > ]In article <1162@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > ]#define max(a,b,lt) ((lt)((a),(b)) ? (b) : (a)) > Not a proper function on args with side effects. Fair enough; the problem here is that C doesn't let you make a variable without making a block. (``The data scope matches the control scope,'' or something like that.) If you don't want to use a static variable (like Fortran parameter passing) here, and you need an expression rather than a block, then you'd better use a true expression language. This is independent of static versus dynamic typing, though. [ function version, given a polymorphic ``any'' type ] > That is a tiny fraction of the whole solution. The whole solution > requires implementation of tagged structures, and garbage collection, > and requires that all of your numbers be specified with constructors > of some type. Yes, so? All of this is hidden below the implementation of ``any'' and operations on it. > First, dynamic typing is a simpler, more general, and more expressive > underlying semantic model. It makes no sense to design a language > under a less satisfactory model and then to patch on the better model > with libraries and preprocessors. First, static typing is a simpler, more general, and more expressive underlying semantic model. (After all, it lets you say what your variable types are, without worrying about some screwy syntax for the job.) It makes no sense to design a language under a less satisfactory model and then to patch on the better model with libraries and preprocessors. (Ya know, assertions like ``x is an int. Really!'') The problem with this argument is that neither model is more general than the other, or simpler, or much more expressive. Given a statically typed language with structs, an ``all'' type, and the set of types, you can set up pairs (value,type) and poof! there's dynamic typing. Given a dynamically typed language with assertions, you can assert that a value has some type and poof! there's static typing. A good preprocessor will smooth out the differences either way; so, all else being equal, you might as well choose the model that produces better code, i.e., static typing (at least with current technology). > Second, it is not possible to tack dynamic typing on a statically > typed language (using a preprocessor and a library) without paying > some penalty somewhere. Dynamic typing needs direct support from the > debugger, for one thing. Yeah, so? As long as your debugger is built in to your interpreter, this is not a problem. > For another, there are optimizations that > the compiler can't do without understanding dynamic typing. For > example if > if (integer(i)) { /* a bunch of code using i */ } [ vs. ] > if (i.tag == INTEGER) { /* a bunch of code using i */ } Ah! A real issue! In Q's any.h, an ``any'' variable comes with a number of constraints: if x.type == anytype(int), for example, then eq(typeof(x.value),int). (Here anytype and typeof are functions evaluated at the preprocessing level; anytype is defined by any.h and produces a run-time value, but typeof merely produces a token that must be used at compile time.) So in your example, it just takes simple data flow analysis for the compiler to understand that i.type won't change inside the block, and hence all operations on i are integer operations. > ]> (4) it is not clear the the ability of > ]> static type declarations to catch errors outweighs the number of > ]> errors that occur in the declarations. > ]Fair enough. However, just in case the benefits of static typing *do* > ]outweigh the errors---possibly by a lot---doesn't it seem wise to > ]provide the support? > Did you see number (3)? Okay, I was just making sure that you didn't mean (4) as a recommendation of any sort. ---Dan
quale@saavik.cs.wisc.edu (Douglas E. Quale) (03/29/91)
In article <1991Mar28.124634.28106@mathrt0.math.chalmers.se> augustss@cs.chalmers.se (Lennart Augustsson) writes: >In article <1991Mar27.161304.17666@daffy.cs.wisc.edu> quale@saavik.cs.wisc.edu (Douglas E. Quale) writes: >>In Common Lisp, >> >>(defun compose (f g) >> (lambda (&rest) (funcall f (apply g &rest)))) >> >>I think it looks better in Scheme, >> >>(define (compose f g) >> (lambda x (f (apply g x)))) >> >>These definitions use dynamic typing to obtain polymorphism. >> >Yes, in LISP it's dynamic typing that gives you the polymorphism, but >you don't have to have dynamic typing. Let's do the same definition >in ML, which is statically typed: > - fun compose(f,g) x = f(g x); > > val compose = fn : (('a -> 'b) * ('c -> 'a)) -> ('c -> 'b) Actually the SML code isn't an exact replacement for the lisp code. In SML the function g must be unary, while the lisp code uses a rest arg so that g is n-ary. I don't know if the type inferencing algorithm used in SML (or in Haskell, re: an earlier post mentioning the same point) can be extended to handle functions that take a variable number of arguments. Some people would say that varargs are bad. I think that they're useful, but I can live without them. I never claimed dynamic typing was *required* for polymorphism, just that it is an inherent benefit. Dan Bernstein doesn't believe that programs written in languages with dynamic typing are shorter than those written in C. My example (which was originally posted by someone else) is merely to show that polymorphism which is automatic for a dynamically typed language makes it easy to write code that is very difficult to duplicate in C. With the exception of the varargs situation, SML and Haskell and Hope and Miranda and Ponder and Orwell and probably many other modern languages have no difficulty with this example. I probably shouldn't use such a mature language as lisp to pick on C, but all the languages mentioned above are younger than C and their type systems are much more powerful. The ANSII C type system would have been state of the art 20 years ago when C was invented, but today it's just a 20 year old type system. I have no bones to pick with modern type systems -- I like SML. The problem with C's type system is that it falls between two stools: it doesn't give you the power and freedom of expression of a dynamic type system, and it also doesn't give you the type safety of a truly strong type system like SML's. (And it certainly doesn't give the polymorphism of either.) The lack of type safety across separately compiled modules is the last nail in C's coffin. Twenty years ago, when C was invented, many programmers thought that they had to program in assembly language because high level languages were too inefficient. Fortunately, some folks looked into the future instead of the past. The success of C helped changed that attitude. Today it's time to look past C. Some won't, of course, but progress is usually slow. -- Doug Quale quale@saavik.cs.wisc.edu
peter@ficc.ferranti.com (Peter da Silva) (03/29/91)
In article <3073:Mar2820:38:5191@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: > A good preprocessor will > smooth out the differences either way; so, all else being equal, you > might as well choose the model that produces better code, i.e., static > typing (at least with current technology). If raw execution speed is the most important criterion, this is true. But then you should probably be coding in assembly. If coding speed is the most important criterion, then you might as well use a dynamically typed language. Use the right tool for the job. Your job, apparently, is pumping bits. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (03/30/91)
Peter da Silva > Me >> >> (2) I generally don't need to make state machines. ... >> Another thing is that usually, I'll want a history of the machine, so >> each state is stored in a separate location. ... > >Hmmm. looks like we're dealing with radically different problem spaces. >You deal with problems where it's desirable and possible to avoid information >loss. I have to deal with that entropy and give up reversibility to allow >unlimited runtime with limited memory storage. This is particularly important >in interactive programs. Hmmm... but my programs are interactive. Example from last week: I wrote a little "diff" style routine to compare two articles. It was implemented as a state machine -- the state history indicated the simplest way to change from program a to program b. I needed to keep this in memory because once I've got how many lines are changed in old and new I give the person the option of displaying only the sort of information that looks interesting (e.g. if article A is intended to replace 3 pages of article B (which is 50 pages) you can extract the exact lines in article A that changes from article B--maybe only a couple words in this application). [my example of dynamic array allocation elided] >It may not be significantly slower, but it uses up significantly more >memory. If list is large enought that can easily make the difference between >being able to run the program and not. > >Also, I tend to work through dynamically allocated lists, or even lists >that are implicitly defined. In that case "max" is unknown and potentially >unbounded. Um, yes.. You have to be careful to break up large problems into manageable chunks. In exchange for that, exception handling becomes simpler (er... assuming a few key operations are atomic). Remember, though, that I'm not working in C. "max" for a dynamic array is implicitly defined, and potentially unbounded, but known. Programming style is a bit different than with lists too. Again, if the problem is too big to be handled as a single piece, you break it up into pieces, and maintain a directory to keep track of the pieces. (Er.. don't confuse the general concept of a directory with specific implementations of a os). > while(msg = GetMsg(port)) > { switch(msg->request) { ... case FOO: state = BAR; } Reply(msg); } >I don't care how GetMsg is implemented, or Reply, but I do need to assume >that there is no limit on the number of times this loop will be run through. >This means I *have* to discard old state information. Er... I didn't mean to say that I never discard state information. I meant to say that I am very careful, when I do so, to make sure that my code will behave properly during exceptions. This may be more important to me than to you, because during debugging it is likely I'll execute code sections multiple times--stopping at a bug, fixing it, then back tracking a short distance and restarting. On the descriptive comments/code thing, consider a language that does not define a comment syntax, but allows you to include constant data wherever syntatically convenient. Then you could put strings in as comments. What the language does with those strings is up to you. (You the programmer, not you the hypothesizer.) Raul Rockwell
anw@maths.nott.ac.uk (Dr A. N. Walker) (03/30/91)
In article <1124@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >It will take a great deal of evidence to convince me that the Algol >committee members were so ignorant that they actually argued over the >implementability of anything in Algol 60. There is nothing in the >language that any knowledgeable person would doubt can be implemented. Whoa! There are two different meanings of "implement" and three different meanings of "Algol" that are being mixed up, if not actually confused. When this started, DG asserted that Algol was [essentially] intended as a clever assembler, and that static typing was intended as an efficiency hack. In response to my queries, he asserted that he intended the earliest Algol tradition [ie Algol 58, or IAL], denied intending Algol 60, and couldn't remember whether he'd actually read the Algol 58 report. ["I rest my case m'lud." However, I cannot resist.] Algol 58: Never [?] implemented, except in the Jovial realisation, probably unimplementable, certainly not designed with machine efficiency in mind. Algol 60: Intended primarily as a descriptive language, and certainly not efficient (call by name, dynamic typing [:-), but consider whether "2^-j" is integer or real, and whether "2" is an integer or a label], dynamic own arrays). No "clever assembler" of the period would have included recursive procedures, or dynamic arrays, for example, as indeed Fortran didn't. I deduce that it was not designed with efficiency in mind; this is confirmed in the pages of Algol Bulletin and in histories of the period (where the whole venture sounds utterly chaotic). Algol 68: Designed to be *run* efficiently, with essentially *no* regard to compilability other than "possible in principle". No-one with a real compiler in mind would have designed a language that needs at least three passes and that cannot be adequately lexed by a finite state machine. There is much in Algol 68 that would make any pre-doc compiler-writer despair; the committee satisfied itself that parsing was technically possible, that code generation was technically possible, and assumed that compiler technology would do the rest. You only have to look at the coercion engines or at the operator identification to assure yourself that highly intelligent people could spend a long time wondering whether something was compilable, and indeed some ambiguities were detected only at a very late stage. Results: Algol 68 is a dream to write, a nightmare to compile, and a bat-out-of-hell to run (given a good compiler!). > But it is >just wrong to claim that they were trying to design a general notation >for describing algorithms without regard to implementation. Well, they were thinking about *computing* algorithms, and more specifically, *numerical* algorithms. Having an eye on what real computers were used for is different from having an eye on how to turn a notation into practical code. > If that >were true they surely would have included sets, strings, graphs, >concatenatable sequences and other useful things in the language. Strings and other useful things *are* in Algol 60 [2 out of 5 isn't bad!]. In 1960, the other things were scarcely recognised as being useful: my undergraduate degree [1961-64] included *no* mention of sets or graphs or concatenation; as an innovation, 5 or so lectures on computing [Edsac Autocode] were included in the NA course. By 1968, the emphasis was on orthogonality and composition from a small number of primitives. Graphs, for example, scarcely count as primitive -- but it is straightforward to write a graph-handling package in Algol. -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (04/01/91)
In article <1991Mar29.012352.23889@daffy.cs.wisc.edu> quale@saavik.cs.wisc.edu (Douglas E. Quale) writes: > Dan Bernstein doesn't believe that programs written in languages with dynamic > typing are shorter than those written in C. When did I say that? Doug, are you capable of correctly paraphrasing anything I've ever said? ---Dan Path: kramden.acf.nyu.edu!brnstnd From: brnstnd@kramden.acf.nyu.edu (Dan Bernstein) Newsgroups: comp.lang.misc Subject: Re: Dynamic typing (part 3) Message-ID: <3352:Mar1803:04:5491@kramden.acf.nyu.edu> Date: Mon Mar 18 03:04:54 GMT 1991 References: <693@optima.cs.arizona.edu> Organization: IR Lines: 8 In article <693@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: > Programs in > dynamically typed languages are generally half to a tenth the size of > programs in statically typed languages that do the same thing. I don't believe you. Give an example. ---Dan
brm@neon.Stanford.EDU (Brian R. Murphy) (04/01/91)
In article <3073:Mar2820:38:5191@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: >The problem with this argument is that neither model is more general >than the other, or simpler, or much more expressive. Given a statically >typed language with structs, an ``all'' type, and the set of types, you >can set up pairs (value,type) and poof! there's dynamic typing. Given a >dynamically typed language with assertions, you can assert that a value >has some type and poof! there's static typing. A good preprocessor will >smooth out the differences either way; so, all else being equal, you >might as well choose the model that produces better code, i.e., static >typing (at least with current technology). Unfortunately, given a statically-typed language with higher-order functions and an "all" type, type inference appears to be undecideable. Thus your statically-typed language _requires_ type declarations, whereas in a dynamically-typed language we can get by without them. Note, however, that I'm not certain of its undecideability. Thatte, in @INPROCEEDINGS{Thatte88, AUTHOR = "S. Thatte", TITLE = "Type Inference with Partial Types", BOOKTITLE = "Automata, Languages and Programming: 15th International Colloquium", MONTH = jul, YEAR = 1988, PAGES = "615-629", Publisher = "Springer-Verlag Lecture Notes in Computer Science, vol. 317"} attempted such a system and failed to find one. The problem is that, whereas in standard ML-like type inference you wind up with a set of equations to solve, here you have a set of inclusion constraints. When all type constructors are monotonic (as all ML type constructors are except for "->"), and your only inclusion rules are: x <= All for all x x <= y if x == y e1(x) <= e2(y) if x <= y and e1(y) <= e2(y) then solving a set of such constraints is pretty simple. If you allow nonmonotonic type constructors: x->y <= a->b if y <= b and a <= x ------ (antimonotonic part) then things get trickier, and it seems quite likely that sets of inclusion constraints cannot in general be solved. If anyone knows a reference for whether or not this problem is decideable, please send me email (as I don't read these type flame wars too carefully). If it is decideable, then I retract my complaint above... -Brian Murphy brm@cs.stanford.edu
boehm@parc.xerox.com (Hans Boehm) (04/02/91)
brm@neon.Stanford.EDU (Brian R. Murphy) writes: >In article <3073:Mar2820:38:5191@kramden.acf.nyu.edu> brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: >>The problem with this argument is that neither model is more general >>than the other, or simpler, or much more expressive. Given a statically >>typed language with structs, an ``all'' type, and the set of types, you >>can set up pairs (value,type) and poof! there's dynamic typing. Given a >>dynamically typed language with assertions, you can assert that a value >>has some type and poof! there's static typing. A good preprocessor will >>smooth out the differences either way; so, all else being equal, you >>might as well choose the model that produces better code, i.e., static >>typing (at least with current technology). >Unfortunately, given a statically-typed language with higher-order >functions and an "all" type, type inference appears to be >undecideable. Thus your statically-typed language _requires_ type >declarations, whereas in a dynamically-typed language we can get by >without them. I think this is getting into an area where we need a bit more precision. The argument implied by Brian seems to be that with a dynamically typed language and some automatic type inference, we can get similar performance to a statically typed language. I think we are no longer addressing reliability issues. The main remaining problem then is that there are a huge number of different formulations of the type inference problem, which vary greatly in the programs to which they can assign types, especially if those programs are library functions for which we have incomplete information about the calling context. Most systems for assigning types to expressions in dynamically typed languages seem to assign basically simple types. They might determine that 3+5 has type integer, but they won't determine that the identity function has type T -> T, for any T. (It sounds like the reference given by Brian is an exception, but it appears to have other problems.) In more practical terms, if list operations aren't built in to the language, and their implementation is in a library, unavailable for reanalysis by the type checker, then I have no way to determine that head(cons(1,NIL)) has type integer. (This is of course exactly the same problem that C++ s type system has with void *; I lose information that's easily statically available. You have no way of keeping track of the fact that two void *'s are the same type.) For statically typed languages without subtyping, I know of the following type inference problems that have been studied: 1. Only simple (Pascal-like) types are assigned. Type inference is easily possible. 2. ML-style polymorphic types are assigned. (The identity function gets a reasonable type. The function that applies its argument to itself (lambda x . x x) is not typable, even though this function can reasonably be applied to the identity function.) Type inference is decidable (and done in ML.) 3. ML modified to allow "polymorphic" functions to be used on the right side of their own (recursive) definitions. Type inference is undecidable. 4. The Girard-Reynolds lambda-calculus. Functions can be parametrized with respect to polymorphic functions. Lambda x . x x can be assigned a type. Type inference is very sensitive to the problem statement. It's either known to be undecidable or an open problem, depending on the exact statement. But there are possible compromises involving smallish amounts of explicit type information. 5. Any of the above with subtyping. I'm not up on all the results here. 6. Some of the above with certain recursive types getting automatically inferred. My impression is that the type inference procedures usually proposed for dynamically typed languages usually are near the weak end of this scale. If they were near the strong end, they would effectively have something similar to a static type system, with nontrivial programmer supplied type assertions. This leaves some interesting questions I can't answer: 1. How much performance do you lose by performing type inference without a notion of polymorphic types? This is probably environment dependent ... 2. Why do most of us program in statically typed languages that are so near the weak end of the above scale that this is all an academic excercise? Ada and C++ are barely at level 2, if you stretch things, and they do require lots of explicit type information. Hans
gudeman@cs.arizona.edu (David Gudeman) (04/02/91)
In article <3073:Mar2820:38:5191@kramden.acf.nyu.edu> Dan Bernstein writes:
]First, static typing is a simpler, more general, and more expressive
]underlying semantic model.
Nonsense. How can it be simpler if it requires more details from the
programmer? Statically typed languages are always more complicated in
the sense that they impose extra restrictions on expressions to make
static typing typing possible. These extra restrictions are
complications. By the same argument, static typing cannot possibly be
more general or more expressive. You can write any expression with
dynamic typing that you can write with static typing, the reverse is
not true. I will not credit the notion that being able to have the
computer check the static types of variables adds to the power or
expressiveness of a language. Those terms are not rigorously defined
at this point, but my intuition rebels at the very notion. Creating
restrictions cannot add to power or expressiveness.
]Ah! A real issue!
If you don't think the other issues are "real", why do you keep
arguing them?
] In Q's any.h, an ``any'' variable comes with a number
]of constraints: if x.type == anytype(int), for example, then
]eq(typeof(x.value),int). (Here anytype and typeof are functions
]evaluated at the preprocessing level; anytype is defined by any.h and
]produces a run-time value, but typeof merely produces a token that must
]be used at compile time.)
I thought so. Your preprocessor is not a preprocessor at all.
If you can write expressions that look just like dynamic typing, then
you have dynamic typing. Implementation details are unimportant. It
is of no value to the programmer to know at what stage of the compiler
the dynamic typing gets done. Dividing compilation into transparent
stages is an unnecessary complexity.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (04/02/91)
In article <1991Mar29.191210.9369@maths.nott.ac.uk> Dr A. N. Walker writes: ]In article <1124@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) ]writes: ] ]>It will take a great deal of evidence to convince me that the Algol ]>committee members were so ignorant that they actually argued over the ]>implementability of anything in Algol 60. There is nothing in the ]>language that any knowledgeable person would doubt can be implemented. ] ] Whoa! There are two different meanings of "implement" and three ]different meanings of "Algol" that are being mixed up, if not actually ]confused. Nope. I said Algol 60 because I meant Algol 60. And _I'm_ not confusing "implement" with "compile", although it looked like you were. ]... In response to my queries, he asserted that he ]intended the earliest Algol tradition [ie Algol 58, or IAL], denied ]intending Algol 60, and couldn't remember whether he'd actually read ]the Algol 58 report. ["I rest my case m'lud." However, I cannot resist.] In the first place, I said the earliest "tradition", not "language" [ie not Algol 58 or IAL]. What I meant was the design philosophy that resulted in Algol 60, including all the decisions that were made before that point. The most important decision being that the language should be compilable. Whether the designers ever mentioned this decision, or were even aware of it, I don't know. But I do know that this was an implicit part of the Algol 60 design. I'm comparing Algol 60 to Lisp and SNOBOL, not to FORTRAN and C. As to the sarcasm: for your information I _have_ read the Algol 60 report and the Algol 68 report, and enough other information to think I have a good feel for the way programming languages evolved. This is not a refereed forum, and I'll not accept being chastized for not making a literature review before posting. ]Algol 60: Intended primarily as a descriptive language, and certainly ] not efficient (call by name, dynamic typing ------- I assume you meant "static". ] No "clever assembler" of the ] period would have included recursive procedures, or dynamic ] arrays, for example, as indeed Fortran didn't. Both features can be compiled. They were less efficient on the machines of the day. ] Strings and other useful things *are* in Algol 60 [2 out of 5 ]isn't bad!]. I should have been more specific: concatenatable, sectionable strings with automatic storage management. Those pathetic things that statically typed languages call "strings" are really character arrays. ] In 1960, the other things were scarcely recognised as ]being useful: Mathematicians had thought they were useful for decades or centuries, and were the designers of Lisp and SNOBOL also recognized their importance. They just didn't fit machine architectures well, so languages designers who were worried about efficiency didn't deal with them. Basically, there are two traditions of programming language design: Type A designers are concerned about efficiency and type B designers are concerned about expressiveness. Historically, you could tell these designs apart because type A languages were compiled and type B languages were interpreted (this no longer holds). Type A languages were designed with the idea that there should be a fairly direct mapping from the language to machine languages. Thus they needed static typing, functions that were defined before compilation, and simple data structures that could be mapped onto machine memory directly. Type B language designers didn't worry about efficiency, and created languages with no simple relationship to machine languages (thus needing to implement them with interpreters). Type B languages almost universally have dynamic typing and generalized data structures with automatic storage management, most also have dynamically created functions. Within each camp, there were different levels of concern for efficiency, but within the type A camp there was always this strong aversion to any feature that would require a lot of runtime support. Algol 60 clearly and unambiguously fits into type A. It is true that _within the type A camp_, they were somewhat cavalier about efficiency, but they still didn't introduce anything that required a great deal of runtime support. In relationship to the programming language community as a whole, they were extremely concerned with efficiency. Only someone who is unfamiliar of the huge type B community would claim that Algol 60 was designed without regard to efficiency. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
cs450a03@uc780.umd.edu (04/02/91)
Caution: about 40 lines of quoted material Hans Boehm > [theorist of types and other maths] Brian Murphy >> [your basic dynamic typing advocate] Dan Bernstein >>> [bit muncher & tty basher extraordinarie] and me, your basic wild-eyed pragmatist >>>The problem with this argument is that neither model is more >>>general than the other, or simpler, or much more expressive. Given >>>a statically typed language with structs, an ``all'' type, and the >>>set of types, you can set up pairs (value,type) and poof! there's >>>dynamic typing. Given a dynamically typed language with assertions, >>>you can assert that a value has some type and poof! there's static >>>typing. [then mentions efficiency of static implementations] Ok, poof, no argument. And I almost hesitate to mention that static language implentations have an efficiency advantage (as they better model things like machine limits) while dynamic language implementations have an efficiency advantage (they better model human thought mechanisms as exemplified by thousands of years of mathematical development). >>... your statically-typed language _requires_ type declarations, >>whereas in a dynamically-typed language we can get by without them. er... which is an example of what I was trying to say. > The main remaining problem then is that there are a huge number of >different formulations of the type inference problem, which vary >greatly in the programs to which they can assign types, especially if >those programs are library functions for which we have incomplete >information about the calling context. Most systems for assigning >types to expressions in dynamically typed languages seem to assign >basically simple types. ... er... simplicity is often an advantage. >For statically typed languages without subtyping, I know of the >following type inference problems that have been studied: > [ 1. (simple) ... 6 (not simple) ] >My impression is that the type inference procedures usually proposed >for dynamically typed languages usually are near the weak end of this >scale. If they were near the strong end, they would effectively have >something similar to a static type system, with nontrivial programmer >supplied type assertions. > >This leaves some interesting questions I can't answer: > >1. How much performance do you lose by performing type inference >without a notion of polymorphic types? This is probably environment >dependent ... environment and application dependent, otherwise you have no metric for performance. Worse, it is rather impractical to rigidly define "without a notion" (not impossible, mind you). >2. Why do most of us program in statically typed languages that are >so near the weak end of the above scale that this is all an academic >excercise? Ada and C++ are barely at level 2, if you stretch things, >and they do require lots of explicit type information. Caution: Rampant assertions follow... In a word, expressiveness. Typing, is very similar to side-effect driven programming, and thus difficult to manage if complex. The advantage of static typing in programming comes from the ability of the typing system to reflect and model machine limits. To take typing beyond this generally means an attempt to characterize computer routines as relations, and to re-define them in terms of abstract domains. In other words, strong typing attempts to make you write each computer "function" twice: once in terms of step-by-step instructions as to what needs to be done, then again in terms of step-by-step instructions as to what it is allowed to do. It is simpler, I believe, to just write the thing once. Or rather, I should say it's simpler for the programmer. The closer you can get to machine language, the simpler things are for the computer. But as has been observed many, many times: 10% of the code takes 90% of the time to execute (or similar statistics). Assuming that programmer time is limited, it makes sense to profile your code and spend most of the programming effort on that 10% CPU hog. (Which, if I understand aright, is what Dan Bernstein is doing: working on optimizing some problems which are CPU hogs on current machines.) Now, it probably some of the topologies that are being developed to describe type systems will have applications which are quite useful. On the other hand, there are some very rich bodies of mathematics which have been developed without these formalisms. Which is to say, "typing can be used to describe a programming" does not mean "typing must be used to describe a program". More specific to programming, it is often more useful to have miscelaneous information about a function (does argument order matter? is it pure computation or does it deal with external objects? does this function apply to any representation of number for which we have a math library, or only a specific word size and specific coding? ...) than it is to concentrate on a completely general description of the function. Finally, note that describing functions as static objects is an outgrowth of a couple very good assembly language practices: (1) describe what's allocated where, and (2) keep the code constant so you can trust it. On the other hand, I find myself more comfortable with a language which allows me to "build up" a function from component functions and utilities than I do with a language which wants me to write as if I were allocating raw memory. In other words, if I have some general function f, it is nice to be able to use it, in all its gory inefficiency and then, once it is tested, make a function g which is f restricted to some domain chosen to reflect properties of the specific problem and the machine I'm working on. I suppose this is partially personal preference, but note that the 90/10 "rule" implies this is a good approach. Also note that this system tends to favor simple types. Function domain is, of course, part of the function definition, but there is a difference between the overall limits a function has for correctness, and the specific limits imposed on a function for machine efficiency. The typing system built into each function, for correctness, is (in general) unique to the definition of that function. In practice, this means you can even define functions which have no domain (always give a domain error on closure), and other odd things like that. Note that this typing usually becomes less complicated when you restrict the problem to a specific machine domain (e.g. 32 bit signed integers). I'm leaving out some key issues (such as the ability of a language to deal with structured data, which is something type systems often try to address), but I think you'll find this rule applies in those areas too: The more direct you are, the simpler the problem. Raul Rockwell
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/02/91)
In article <1APR91.23564447@uc780.umd.edu> cs450a03@uc780.umd.edu writes: > >Ok, poof, no argument. And I almost hesitate to mention that static >language implentations have an efficiency advantage (as they better >model things like machine limits) while dynamic language >implementations have an efficiency advantage (they better model human >thought mechanisms as exemplified by thousands of years of >mathematical development). > People keep saying this, but it just ain't so. Type declarations are an integral part of mathematics. Abbreviations and conventions, which make declarations implicit are used extensively, but that's not the same as no type declarations. Type declarations are so common as to be cliches in mathematical speech: e.g. "let G be a group", "let P be a proposition", "let R be a ring", "(forall x in X)" .... Maybe I'm missing an essential difference between these type declarations, and the declarations of programming languages. If so, I'd be happy if someone could explain.
peter@ficc.ferranti.com (Peter da Silva) (04/02/91)
Fine. There are type A languages and type B languages. There are good reasons for having both. I prefer type A languages because they are simpler to implement, which means I can get a decent implementation of them on a consumer machine. I would love to have a good type B language on my Amiga, though I'd still use a type A one for a lot of stuff: real-time response seems to require it (I can't see doing MIDI processing in Lisp on a 68000). The problem is, a *decent* type B language compiler is about the size of GCC (an *excellent* type A language compiler). They won't fit on PCs, and the market share of other consumer machines is just too small. *MY* challenge: give me an alternative. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
peter@ficc.ferranti.com (Peter da Silva) (04/03/91)
In article <962@mgt3.sci.UUCP> dc@mgt3.sci.com (D. C. Sessions) writes: > The upshot of this little affair was the conversion of an entire shop > full of C hackers into Modula-2 fanatics, purely because they *never* > wanted to give up intermodule type-safety again. My response is "convert to ANSI C, which gives you intermodule type safety". > So: for the purposes of the current discussion, how do our ideal > dynamically-typed languages ensure that a similar little > misunderstanding doesn't happen again? That's easy: the function that gets a foo wouldn't misinterpret it as being a bar in the first place. It'd either have kicked out an error, or the test for bar.magic would have failed. -- Peter da Silva. `-_-' peter@ferranti.com +1 713 274 5180. 'U` "Have you hugged your wolf today?"
cs450a03@uc780.umd.edu (04/03/91)
Victor Yodaiken > Me >> >>static language implentations have an efficiency advantage (as they >>better model things like machine limits) while dynamic language >>implementations have an efficiency advantage (they better model >>human thought mechanisms as exemplified by thousands of years of >>mathematical development). >People keep saying this, but it just ain't so. Type declarations are >an integral part of mathematics. Abbreviations and conventions, which >make declarations implicit are used extensively, but that's not the >same as no type declarations. Did I say no type declarations? I said dynamic typing. The advantage of dynamic typing is not "no type declarations", the advantage is that redundant type declarations can be eliminated. [Actually, this property is not unique to dynamically typed languages. But DTLs are more consistent in this regard than are STLs.] For example, if F(x) is defined as G(h(q(x))), and the domain of q is balogna sandwiches, then the domain of F is balogna sandwiches, and x can only take on values that are balogna sandwiches. Using q in this manner is sufficient to declare the type of F. Another advantage of dynamic typing is that you get a defined behavior when a function gets a value which is out of its domain (e.g. divide by zero, or array index with index too large). Again, this is not unique to DTLs -- and again, DTLs are more consistent about this than STLs. Raul Rockwell
cs450a03@uc780.umd.edu (04/03/91)
I wrote: >For example, if F(x) is defined as G(h(q(x))), and the domain of q is >balogna sandwiches, then the domain of F is balogna sandwiches, and >x can only take on values that are balogna sandwiches. Using q in >this manner is sufficient to declare the type of F. gack This should say "Using q in this manner is sufficient to declare the domain (argument type) of F." The result type of F depends on G. Raul
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/03/91)
In article <3APR91.00020019@uc780.umd.edu> cs450a03@uc780.umd.edu writes: >Did I say no type declarations? I said dynamic typing. > >The advantage of dynamic typing is not "no type declarations", the >advantage is that redundant type declarations can be eliminated. >[Actually, this property is not unique to dynamically typed languages. >But DTLs are more consistent in this regard than are STLs.] > >For example, if F(x) is defined as G(h(q(x))), and the domain of q is >balogna sandwiches, then the domain of F is balogna sandwiches, and >x can only take on values that are balogna sandwiches. Using q in >this manner is sufficient to declare the type of F. > This makes sense, but it seems to be an instance of an abbreviation or convention. There is a preface: all functions are asumed to be over balogna sandwiches, implied in what you write. If what you mean by "dynamic typing" is simply greater facility in defining domains and higher level declarations of type (e.g., all functions in the block are over integers), then I believe that I understand. But, it appeared from earlier discussion that dynamic typing was more involved. >Another advantage of dynamic typing is that you get a defined behavior >when a function gets a value which is out of its domain (e.g. divide >by zero, or array index with index too large). Again, this is not >unique to DTLs -- and again, DTLs are more consistent about this than >STLs. > I'm really lost now. What is the connection between type dynamism and well definedness of operations?
cs450a03@uc780.umd.edu (04/04/91)
Victor Yodaiken > [responding to an illustration of how constructing a function specifies the type of the function] >This makes sense, but it seems to be an instance of an abbreviation >or convention. There is a preface: all functions are asumed to be >over balogna sandwiches, implied in what you write. If what you mean >by "dynamic typing" is simply greater facility in defining domains >and higher level declarations of type (e.g., all functions in the >block are over integers), then I believe that I understand. But, it >appeared from earlier discussion that dynamic typing was more >involved. Dynamic typing is simple in *concept*. The problem is that it often does not map well onto machine architecture. For example, if you have a function which is defined over the domain of numbers (take addition, for example), then you usually see a dynamic typing system faithfully providing that functionality for a variety of machine implementations of numeric types. There is no reason you couldn't have just a single numeric type (say complex numbers where both real and imaginary components are 96 bit IEEE floats). Except that's pretty silly when all you want to do is represent array indices. Or worse, 1's and 0's. >>Another advantage of dynamic typing is that you get a defined behavior >>when a function gets a value which is out of its domain (e.g. divide >>by zero, or array index with index too large). Again, this is not >>unique to DTLs -- and again, DTLs are more consistent about this than >>STLs. >I'm really lost now. What is the connection between type dynamism and >well definedness of operations? Another implementation detail of dynamic types is that there is a lot more information present than is usually acted on. For example, if you have a function f(x) defined as F(g(x), h(x)), and g(x) is valid over the integer domain, and h(x) is valid only for non-negative numbers, then the domain of f is unsigned integers. If f where compiled, the compiler could probably make good use of this fact, and issue warnings or error messages if f where used (a) where it is possible to give f values outside of its domain or (b) where it is guaranteed that f will be given values outside of its domain. A similar situation might be where the domain of f is integers greater than negative 8. In a statically typed language, type is considered to be a property of variables, so if a variable can have a value outside the domain of a function then that is not considered a type error. In a dynamically typed language, where practically all functions have type checks to ensure their arguments' validity, more is usually checked than just the "wordsize" or the "typetag" of the arguments. Statically typed languages leave many such details to the programmer. The advantage is that it is possible to write fast programs, simply by leaving out some of the type-checks. The costs are that errors may not be caught as soon as they would be otherwise, and that the programmer must spend a fair bit of his time programming and debugging type structures. Raul Rockwell
jgk@osc.COM (Joe Keane) (04/04/91)
In article <22MAR91.20485982@uc780.umd.edu> cs450a03@uc780.umd.edu writes: >Generally, each assignment to a variable is unique. (I try not to >re-assign, and when I do, I try and make sure re-executing that >section of code would not cause a problem). Exception made for loop >counters, but not for other assignments made within the loop. In article <039AIL3@xds13.ferranti.com> peter@ficc.ferranti.com (Peter da Silva) writes: >This is an unusual coding style, in my experience. Are you actually >limitin assignments, or are you hiding those assignments in call by >reference? Perhaps a code fragment would help. I don't think this is so unusual. I do the same thing to a large extent when programming in C or C++. Perhaps it could be my exposure to functional languages, but i find that this approach goes well with C too. I'm not strict about it, but i find that almost all variables can be done this way. We have to make a few exceptions, which i'll describe later. There's another thing i do which goes together with this. I generally try to keep variables' scope as small as possible. For example, if a variable is used inside a loop but it's not carried between iterations, then i can declare it inside the loop body. Given the `small as possible' rule, that means i should declare it there. Sometimes i make blocks just to declare variables in. This may sound crazy, but it makes sense because these blocks generally correspond to some conceptual operation. I think the result is that code written this way is easier to read and understand what's going on. By making the lifetime of a variable clear, you help the human reader understand the general structure of the algorithm. Optimizing compilers can figure it out too, but people don't want to spend time doing this. Loops are an exception to this rule. Obviously, you have to increment or somehow advance your loop variable in the loop, or else it gets stuck. And often the point of a loop is to accumlate some value. You asked for an example. I don't think a short example would illustrate the point well. So free with this posting, i'm going to give you a real program. This is actually a useful program, so you may want to save the good version. First i show the `bad' version of the program. It suffers from what i call `Pascal variable declaration syndrome'. All the variables are declared at the top of the function, far away from where they're actually used. You can't see where variables are used or how long they're expected to live. Variables are re-used without this being clear. You may say that the problem is that my main function is too big. I don't agree with this, although i suppose this is a matter of taste. I believe that chopping it up into little functions would not improve readability. With proper attention to scoping, which is what i'm talking about after all, each block is like a small function. But the blocks are ordered exactly like they're used, so you don't have to go jumping all around the source to see something simple. -- don't cut here -- don't cut here -- don't cut here -- don't cut here -- #include <stdio.h> #include <sys/time.h> extern char* malloc(); extern char* realloc(); struct line { int length; char* ptr; }; int main (argc, argv) int argc; char* argv[]; { struct line* master_ptr; int master_size; int master_capacity; char *buffer_ptr; int buffer_capacity; int c; int buffer_size; char* line_ptr; int pass; struct timeval tv; int line_number; int other_line; struct line temp; char* ptr; char* end; master_capacity = 256; master_ptr = (struct line*)malloc(master_capacity * sizeof(struct line)); if (!master_ptr) goto out_of_memory; master_size = 0; buffer_capacity = 256; buffer_ptr = malloc(buffer_capacity); if (!buffer_ptr) goto out_of_memory; for (;;) { c = getchar(); if (c == EOF) goto eof; if (master_size >= master_capacity) { master_capacity *= 2; master_ptr = (struct line*)realloc(master_ptr, master_capacity * sizeof (struct line)); if (!master_ptr) goto out_of_memory; } buffer_size = 0; while (c != '\n') { if (buffer_size >= buffer_capacity) { buffer_capacity *= 2; buffer_ptr = realloc(buffer_ptr, buffer_capacity); } buffer_ptr[buffer_size] = c; buffer_size++; c = getchar(); if (c == EOF) { fputs("shuffle: adding newline at end of file\n", stderr); break; } } line_ptr = malloc(buffer_size); if (!line_ptr) goto out_of_memory; memcpy(line, buffer_ptr, buffer_size); master_ptr[master_size].length = buffer_size; master_ptr[master_size].ptr = line_ptr; master_size++; if (c == EOF) goto eof; } eof: free(buffer_ptr); for (pass = 0; pass < 16; pass++) { gettimeofday(&tv, 0); srandom(tv.tv_sec ^ tv.tv_usec); for (line_number = 0; line_number < master_size; line_number++) { other_line = random() % (master_size - line_number) + line_number; temp = master_ptr[line]; master_ptr[line_number] = master_ptr[other_line]; master_ptr[other_line] = temp; } } for (line_number = 0; line_number < master_size; line_number++) { ptr = master_ptr[line_number].ptr; end = ptr + master_ptr[line_number].length; while (ptr < end) { putchar(*ptr); ptr++; } putchar('\n'); } return 0; out_of_memory: fputs("shuffle: out of memory\n", stderr); return 1; } -- don't cut here -- don't cut here -- don't cut here -- don't cut here -- Now i'm going to show you how i actually wrote it. None of the variables are changed, but they're declared in the right place. I've also added a couple blocks, like i said before. I've also restored some debugging code, because you may find it useful. It also satisfies the single-assignment property much better than the `bad' version, because variables are created when they're needed rather than at the beginning of the function. -- cut here -- cut here -- cut here -- cut here -- cut here -- cut here -- #include <stdio.h> #include <sys/time.h> #define DEBUG 0 extern char* malloc(); extern char* realloc(); struct line { int length; char* ptr; }; int main (argc, argv) int argc; char* argv[]; { struct line* master_ptr; int master_size; #if DEBUG fputs("shuffle: reading lines...\n", stderr); #endif { int master_capacity; char *buffer_ptr; int buffer_capacity; master_capacity = 256; master_ptr = (struct line*)malloc(master_capacity * sizeof(struct line)); if (!master_ptr) goto out_of_memory; master_size = 0; buffer_capacity = 256; buffer_ptr = malloc(buffer_capacity); if (!buffer_ptr) goto out_of_memory; for (;;) { int c; int buffer_size; char* line_ptr; c = getchar(); if (c == EOF) goto eof; if (master_size >= master_capacity) { master_capacity *= 2; master_ptr = (struct line*)realloc(master_ptr, master_capacity * sizeof (struct line)); if (!master_ptr) goto out_of_memory; } buffer_size = 0; while (c != '\n') { if (buffer_size >= buffer_capacity) { buffer_capacity *= 2; buffer_ptr = realloc(buffer_ptr, buffer_capacity); } buffer_ptr[buffer_size] = c; buffer_size++; c = getchar(); if (c == EOF) { fputs("shuffle: adding newline at end of file\n", stderr); break; } } line_ptr = malloc(buffer_size); if (!line_ptr) goto out_of_memory; memcpy(line_ptr, buffer_ptr, buffer_size); master_ptr[master_size].length = buffer_size; master_ptr[master_size].ptr = line_ptr; master_size++; if (c == EOF) goto eof; } eof: free(buffer_ptr); } #if DEBUG fprintf(stderr, "shuffle: total of %d lines read\n", master_size); #endif { int pass; for (pass = 0; pass < 16; pass++) { struct timeval tv; int line_number; gettimeofday(&tv, 0); #if DEBUG fprintf(stderr, "shuffle: doing pass %d, time is %d seconds, %d microseconds...\n", pass, tv.tv_sec, tv.tv_usec); #endif srandom(tv.tv_sec ^ tv.tv_usec); for (line_number = 0; line_number < master_size; line_number++) { int other_line; struct line temp; other_line = random() % (master_size - line_number) + line_number; temp = master_ptr[line_number]; master_ptr[line_number] = master_ptr[other_line]; master_ptr[other_line] = temp; } } } #if DEBUG fputs("shuffle: writing lines...\n", stderr); #endif { int line_number; for (line_number = 0; line_number < master_size; line_number++) { char* ptr; char* end; ptr = master_ptr[line_number].ptr; end = ptr + master_ptr[line_number].length; while (ptr < end) { putchar(*ptr); ptr++; } putchar('\n'); } } #if DEBUG fputs("shuffle: all done\n", stderr); #endif return 0; out_of_memory: fputs("shuffle: out of memory\n", stderr); return 1; } -- cut here -- cut here -- cut here -- cut here -- cut here -- cut here -- -- Joe Keane, C++ hacker jgk@osc.com (...!uunet!stratus!osc!jgk)
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/05/91)
In article <3APR91.20574161@uc780.umd.edu> cs450a03@uc780.umd.edu writes: >Victor Yodaiken > > [responding to an illustration of how constructing a function > specifies the type of the function] >>This makes sense, but it seems to be an instance of an abbreviation >>or convention. There is a preface: all functions are asumed to be >>over balogna sandwiches, implied in what you write. If what you mean >>by "dynamic typing" is simply greater facility in defining domains >>and higher level declarations of type (e.g., all functions in the >>block are over integers), then I believe that I understand. But, it >>appeared from earlier discussion that dynamic typing was more >>involved. > >Dynamic typing is simple in *concept*. The problem is that it often >does not map well onto machine architecture. For example, if you have >a function which is defined over the domain of numbers (take addition, >for example), then you usually see a dynamic typing system faithfully >providing that functionality for a variety of machine implementations >of numeric types. > Your explanation is confusing me even more. Most of the programming languages that I know do something like this. For example, x +y works fine in "C" for floating and integer types, and can also be extended to other types in C++ I believe. Maybe a precise definition of "dynamic typing" would be useful. From previous postings, I had the impression that "dynamic typing" involved compiler/run-time inferences about the type of a function or variable from the context and use of the variable or function. On the other hand, "static typing" requires the programmer to indicate exactly the type of the object. Is this incorrect? >In a statically typed language, type is considered to be a property of >variables, so if a variable can have a value outside the domain of a >function then that is not considered a type error. In a dynamically >typed language, where practically all functions have type checks to >ensure their arguments' validity, more is usually checked than just >the "wordsize" or the "typetag" of the arguments. > Again, I'm not following you here. Is this extra type checking in dynamic languages part of the language or a coding style? Clearly there are limits to how much the compiler can do: e.g. x * limit(f,x, x-> 0) is a "type" error of some kind if f(x) does not converge --- multiplication has only numbers in its domain.
new@ee.udel.edu (Darren New) (04/06/91)
In article <28875@dime.cs.umass.edu> yodaiken@chelm.cs.umass.edu (victor yodaiken) writes: >Your explanation is confusing me even more. >Maybe a precise definition of "dynamic typing" would be useful. Dynamic typing is when the type of a *variable* is unknown (and unspecified) at compile time. Any given value assigned to that variable will have a type, but the variable does not restrict what types of values may be assigned to it. Contrast these: int f(float f, int i) { blah blah blah } /* here, f must only get floats and i must only get ints. */ (defun f (f i) (blah blah )) Here, f may be a float, and integer, a bignum, a list, a closure, etc. Basically, expressions, variables, function names, and so on do not have types. Nothing that is purely syntactical has types. Only actual values have types. -- Darren -- --- Darren New --- Grad Student --- CIS --- Univ. of Delaware --- ----- Network Protocols, Graphics, Programming Languages, FDTs ----- +=+=+ My time is very valuable, but unfortunately only to me +=+=+ + When you drive screws with a hammer, screwdrivers are unrecognisable +
cs450a03@uc780.umd.edu (04/06/91)
Victor Yodaiken writes: >>[Raul:] For example, if you have a function which is defined over the >>domain of numbers (take addition, for example), then you usually see >>a dynamic typing system faithfully providing that functionality for >>a variety of machine implementations of numeric types. >Your explanation is confusing me even more. sorry.. >Most of the programming languages that I know do something like this. >For example, x +y works fine in "C" for floating and integer types, >and can also be extended to other types in C++ I believe. But in C, you have to go through some red tape if you need to store both integer and floating types in the same variable. You also have to go through this red tape to define a function that takes more than one type as an argument (for example, functions which manage a run-time symbol table). C++ allows you a little more abstraction, but still requires quite a bit of paper work. >Maybe a precise definition of "dynamic typing" would be useful. See Darren New's article (5 April, I think, same Subject: as this article). >From previous postings, I had the impression that "dynamic typing" >involved compiler/run-time inferences about the type of a function or >variable from the context and use of the variable or function. On >the other hand, "static typing" requires the programmer to indicate >exactly the type of the object. Is this incorrect? Dynamic typing requires compiler type inferences (though these are trivial in the presence of declarations, or well-conditioned code). There is no requirement for run-time type inferences. Type information is always available at run-time. If a compiler can not infer type, it will provide for run-time type checking as well--it's fairly trivial to implement. >>[Me (Raul) again:] ... In a dynamically typed language, where >>practically all functions have type checks to ensure their >>arguments' validity, more is usually checked than just the >>"wordsize" or the "typetag" of the arguments. >Again, I'm not following you here. Is this extra type checking in >dynamic languages part of the language or a coding style? For language primitives, it's part of the language. For user defined functions, it is both [the programmer must provide any necessary type checking that is not implicit in the primitives]. Raul Rockwell
cs450a03@uc780.umd.edu (04/06/91)
Victor Yodaiken > Darren New >> >>Dynamic typing is when the type of a *variable* is unknown (and unspecified) >>at compile time. Any given value assigned to that variable will have >>a type, but the variable does not restrict what types of values may >>be assigned to it. I just realized: that should begin "Dynamic typing is when the type of a variable _may_ be unknown at compile time." >Makes sense. But, then I don't believe that dynamic typing is similar >to standard mathematical usage. In fact, it seems that the trend in >mathematics has been towards greater use of "static typing". The >problem with "untyped" expressions is that they are inherently >ambiguous. If I'm writing about semigroups and regular languages I >must tell you when a*b is concatenation and when it is semigroup >addition. Here, * is a variable, and "telling me when a*b is cantenation" "and when it is semigroup addition" are both declarations (or assignment statements). That can get confusing, I agree, which is a good reason for using a separate symbol for each usage. Raul Rockwell
cs450a03@uc780.umd.edu (04/06/91)
I wrote (paraphrasing Darren New) >"Dynamic typing is when the type of a variable _may_ be unknown at >compile time." An even yet more exactly stated version would be :-) "Dynamic typing is when the most efficient of the available primitive machine representation for some value(s) is chosen at run-time." An analogy to C might be choosing an "array of short" or "array of long" depending on the size of the values. (I'd say "array of double" but C has completely different semantics for "/" for that case.) Raul Rockwell
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/06/91)
In article <49907@nigel.ee.udel.edu> new@ee.udel.edu (Darren New) writes: > >Dynamic typing is when the type of a *variable* is unknown (and unspecified) >at compile time. Any given value assigned to that variable will have >a type, but the variable does not restrict what types of values may >be assigned to it. Contrast these: > >int f(float f, int i) { blah blah blah } >/* here, f must only get floats and i must only get ints. */ > >(defun f (f i) (blah blah )) >Here, f may be a float, and integer, a bignum, a list, a closure, etc. > >Basically, expressions, variables, function names, and so on do not have >types. Nothing that is purely syntactical has types. Only actual >values have types. -- Darren Makes sense. But, then I don't believe that dynamic typing is similar to standard mathematical usage. In fact, it seems that the trend in mathematics has been towards greater use of "static typing". The problem with "untyped" expressions is that they are inherently ambiguous. If I'm writing about semigroups and regular languages I must tell you when a*b is concatenation and when it is semigroup addition. Figuring this out from the context alone is difficult for most people. What makes classical programming languages so annoying, however, is that the type schemes are so much more clumsy than we are used to in mathematics. At the simplest level, one might have a hierarchy of types of objects, and demand that when two objects appear together, they are always converted to the type that appears highest on the hierarchy. In practice, however, types of mathematical objects do not form any kind of strict hierarchy, In some respects,they form a network, or directed graph, and one could imagine always converting objects to the type of their common ancestor" in the network. But even this much more elaborate mechanism does not adequately capture mathematical usage. Different forms of algebraic expressions, say factored and expanded out, can be thought of as different types. In most cases, the rules for when to convert between these types are much more complicated than you can represent by a simple fixed network of type conversions. Stephen Wolfram, Mathematica
olson@lear.juliet.ll.mit.edu ( Steve Olson) (04/07/91)
In article <28924@dime.cs.umass.edu> yodaiken@chelm.cs.umass.edu (victor yodaiken) writes:
The problem with "untyped" expressions is that they are inherently
ambiguous. If I'm writing about semigroups and regular languages I must
tell you when a*b is concatenation and when it is semigroup addition.
Figuring this out from the context alone is difficult for most people.
This sounds more like overuse of operator overloading rather than anything
having to do with static vs. dynamic typing. This problem could occur just as
easily in C++ as in CLOS. If you never want to mix the two types then
renaming the operators will remove all confusion. If you do want to mix the
two types then the problem is the same whether you are using a dynamically
typed language or a statically typed sortof-dynamic-typing-through-subclassing-
and-virtual-functions language.
- Steve Olson
MIT Lincoln Laboratory
olson@juilet.ll.mit.edu
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/07/91)
In article <OLSON.91Apr6155734@lear.juliet.ll.mit.edu> olson@lear.juliet.ll.mit.edu ( Steve Olson) writes: > >In article <28924@dime.cs.umass.edu> yodaiken@chelm.cs.umass.edu (victor yodaiken) writes: > The problem with "untyped" expressions is that they are inherently > ambiguous. If I'm writing about semigroups and regular languages I must > tell you when a*b is concatenation and when it is semigroup addition. > Figuring this out from the context alone is difficult for most people. > >This sounds more like overuse of operator overloading rather than anything >having to do with static vs. dynamic typing. This problem could occur just as >easily in C++ as in CLOS. If you never want to mix the two types then >renaming the operators will remove all confusion. If you do want to mix the >two types then the problem is the same whether you are using a dynamically >typed language or a statically typed sortof-dynamic-typing-through-subclassing- >and-virtual-functions language. I don't want to have to give up overloading -- one of the real conveniences of mathematical notation. But if I declare variable types, there is no problem. So, if I write: let a,b be elements of the alphabet and let a',b' be the corresponding elements of the semigroup, then a*b and a'*b' are easy to distinguish. By making the types of the variables explicit, I make the overloading of * unambiguous. An expression a*b' where the two variables have different types makes no sense in this context and I'd like the compiler to catch it.
gudeman@cs.arizona.edu (David Gudeman) (04/08/91)
In article <28673@dime.cs.umass.edu> victor yodaiken writes: ]In article <1APR91.23564447@uc780.umd.edu> cs450a03@uc780.umd.edu writes: ]...dynamic language[s]... better model human ]>thought mechanisms as exemplified by thousands of years of ]>mathematical development). ]> ] ]People keep saying this, but it just ain't so. Type declarations are ]an integral part of mathematics... ]Maybe I'm missing an essential difference ]between these type declarations, and the declarations of programming ]languages. If so, I'd be happy if someone could explain. OK, try this. In mathematics you don't have to describe the types of objects unless the type is important. In statically typed languages, you have to declare the type whether it has any relevance or not (or fix it at compile time whether it is really knowable or not). In mathematics or dynamically typed languages you can describe the concept of a set or a sequence, and operations over those data structures, without refering to the "type" of elements. For example in math you could say If f is a function and s is a sequence, then map(f,s) is the sequence t such that for all i . t[i] = f(s[i]). In a Icon (a dynamically typed language) you could define procedure map(f,s) local t,i t := list(*s) every i := 1 to *s do t[i] := f(s[i]) return t end Try to write this function in a statically typed language so that it has all the generality of the math and Icon versions. (Actually, the math version works for infinite sequences and the Icon one doesn't. There are languages that fix that...) No, forget the challenge. Someone is sure to post a solution (or near solution) using some baroque system of static declarations from Ada or some wierd C trick (I can think of one...). The point is that even if you can sort of do it in these languages, it is obvious that the Icon version is much closer to the mathematical (I say "reasonable" or "natural") way of doing it. Furthermore, when you do use something like a type declaration in mathematics, it is to disambiguate or add clarify, and the nature of the declarations reflects that. In statically typed programming languages the purpose of the types is to let the compiler generate better code, and the nature of the declarations reflects that. For example, you cannot simply declare something as a "number", you have to decide whether you want it represented in floating point format or integer, and what size you want. You can't declare that something is a set, you have to implement a set using fixed-size memory blocks. I just depends on what you want in a programming language. If you think that programming languages should always give fairly transparent compilation, or are willing to go to a great deal of extra work for a few extra cycles, then you won't be too happy with dynamic typing. But it is just ridiculous to claim that static typing is more natural than dynamic typing -- I am talking here about static typing in the sense that _every_ expression has to be give a fixed type of the form found in programming languages. I only have to show one example of "dynamic typing" in mathematics to show that mathematics is not "statically typed" in the universal sense of statically typed programming languages (and I did so above). -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (04/08/91)
In article <6APR91.10374005@uc780.umd.edu> cs450a03@uc780.umd.edu writes:
]...
]"Dynamic typing is when the most efficient of the available primitive
]machine representation for some value(s) is chosen at run-time."
I don't like that definition at all. In the first place, it is an
implementation-based definition rather than a semantic one. In the
second place, I don't know of any dynamically typed language that
claims to choose the "most efficient ... representation" -- they
choose a convenient representation.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/08/91)
In article <1593@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >Furthermore, when you do use something like a type declaration in >mathematics, it is to disambiguate or add clarify, and the nature of >the declarations reflects that. In statically typed programming >languages the purpose of the types is to let the compiler generate >better code, and the nature of the declarations reflects that. For >example, you cannot simply declare something as a "number", you have >to decide whether you want it represented in floating point format or >integer, and what size you want. You can't declare that something is >a set, you have to implement a set using fixed-size memory blocks. > Agreeing that the type declarations in classical algol/fortran/c ... programming languages are hopelessly rigid, I'm not sure that "dynamic" versus "static" is at the core of the argument. There seems no reason why we cant have a "static" programming language in which type declarations are more flexible than current definitions. Thus, if I declare "x,y" to range over numbers, then "x+y, x*y " are well defined, but "x/y" is not, because the semantics of / depends on more than the numberness of the arguments -- i.e. integer/integer -> integer, float/float -> float, integer/integer ->(q,r) are all different functions. I sympathize with those who find it absurd to have to describe variables by storage cell length ( essentially the "c" model) but I still don't see what this has to do with dynamic "run-time" type checking. Even with function variables, it seems plausible that one could be forced to provide a proof that any expression x(y,z) should be type consistent: (y,z) in Domain(x). Why should this proof be given in temrs of a dyanmic test, rather than a "compile time" calculation?
bbc@rice.edu (Benjamin Chase) (04/08/91)
gudeman@cs.arizona.edu (David Gudeman) writes: > OK, try this. In mathematics you don't have to describe the types > of objects unless the type is important. In statically typed > languages, you have to declare the type whether it has any relevance > or not (or fix it at compile time whether it is really knowable or > not). As we shall see below, delaring the type may not be quite as painful as you suggest. > In mathematics or dynamically typed languages you can describe the > concept of a set or a sequence, and operations over those data > structures, without refering to the "type" of elements. For example > in math you could say > If f is a function and s is a sequence, then map(f,s) is the > sequence t such that for all i . t[i] = f(s[i]). > In a Icon (a dynamically typed language) you could define > procedure map(f,s) > local t,i > t := list(*s) > every i := 1 to *s do t[i] := f(s[i]) > return t > end > Try to write this function in a statically typed language so that it > has all the generality of the math and Icon versions. I think you can write "map" in Russell, which is statically typed. My Russell is a little rusty, so I could be wrong. There might be a hitch with the declaring the result type of "map"; I'm not really certain either way. This works by using a different notion of types and polymorphism than you were probably expecting... The type declarations for "map" look sort of parameterized in Russell. Informally, they'd run something like map(f:S->T; s:list of S) : list of T; S has {V} /* value of (ie. rvalue of an lvalue, basically) */ T has {V,:=} /* value of, assignment operator */ { } I'm certain that I've got the syntax wrong. Anyhow, the gist is that I require the types S (type of elements of s) and T (types of elements of t) to support certain operations. This definitely has an o-o feel to it, yes, and it might be helpful for purposes of your comprehension to rephrase it as "classes S and T must support certain methods, named V and :=". > ... Furthermore, when you do use something like a type declaration > in mathematics, it is to disambiguate or add clarify, and the nature > of the declarations reflects that. In statically typed programming > languages the purpose of the types is to let the compiler generate > better code, and the nature of the declarations reflects that. Such an efficiency-minded purpose is not so apparent in Russell. I found the feel of type declarations for a polymorphic function to be more of a flavor of "to use the function, your argument types must support the following operations: ...". That seemed like a very natural requirement to me, one that would hold for most languages, either at compile time or at run time. I liked it from a software-engineering aspect, too. > I only have to show one example of "dynamic typing" in mathematics > to show that mathematics is not "statically typed" in the universal > sense of statically typed programming languages (and I did so > above). Careful. You may have instead shown a narrow view of what is actually possible. Now, having said all this, I will say that I am not an unflagging fan of static typing nor of Russell. There are hard problems in Russell regarding arrays, to which I never quite found a good solution. I recall it being difficult to write a completely general-purpose matrix math package, without having explicit (ie. run-time) checks that the sizes of arrays matched appropriately. In particular, if you want to code up a matrix multiply operation: mult(A: array[m,n] of T; B: array[o,p] of T) : array [m,p] of T ... { /* Ooops, need to basically do a run-time type check */ if (n != o) { print("Hey, these arrays can't be multiplied!"); print("Their sizes don't match!"); } } (Of course, Russell's polymorphism over the element type T does work like a champ. Oh, yeah, and it supports operator overloading and precendence, so the above function was actually called "*", and you'd say "A*B", like you'd want.) So, I guess I'd like something like Russell, but maybe with a dynamically typed loophole. I'd tell the compiler what I know, or what I care to let it know, about my code. It would figure out what it can (and perhaps informs me of what is left unchecked), and then puts off the rest of the checking until run-time. I wish there had been a way for the above run-time array size check to be done automagically (I suppose you'd declare the arrays mxn and nxp then, and unify the two n's?), and for the handling of such an error to be done in some standard fashion (Modula-3 style raises/exceptions?). -- Ben Chase <bbc@rice.edu>, Rice University, Houston, Texas
cs450a03@uc780.umd.edu (04/09/91)
I wrote: "Dynamic typing is when the most efficient of the available primitive machine representation for some value(s) is chosen at run-time." David Gudeman wrote: I don't like that definition at all. In the first place, it is an implementation-based definition rather than a semantic one. Darren New wrote: I don't think it has anything to do with machine representations or efficiency. Ok, ok.. I apologize. My fault for using such an inexact expression as "most efficient"... [My thoughts were along the line of "most efficient of the implemented types for the computational model" but even that isn't very useful as a description.] Umm... the concept I was trying to capture was something along the lines of the difference between a type-tag + a c-struct vs a type tag + a c-union. But that is totally implementation, and only one of many possible implementations at that. (First alternate implementation that comes to mind the case where address ranges are used to distinguish between different word sizes. ... still only vaguely related to dynamic/static typing). Once again, sorry. Raul Rockwell
new@ee.udel.edu (Darren New) (04/09/91)
In article <6APR91.10374005@uc780.umd.edu> cs450a03@uc780.umd.edu writes: >I wrote (paraphrasing Darren New) >>"Dynamic typing is when the type of a variable _may_ be unknown at >>compile time." Actually, unless you are talking about type inferencing, I would say "*must* be unknown." If the type is known at compile time, it is static typing, period. If you don't specify the type but the compiler can figure it out for you, then that is static typing, because the type of the values assigned to the variable is known statically. I never intended to imply that dynamic typing and static typing cannot be mixed in the same language. >An even yet more exactly stated version would be :-) >"Dynamic typing is when the most efficient of the available primitive >machine representation for some value(s) is chosen at run-time." I don't think it has anything to do with machine representations or efficiency. Your definition would exclude Objective-C, Smalltalk, C++, and every other dynamically typed language I know except APL. For example, Smalltalk does not chose the most efficient version of the data: it uses what the programmer defines. Maybe languages like Mathematica or ML do such a thing, but chosing an efficient representation is orthagonal to dynamic typing. For example, in Ada (as I understand it), one can say "I need a float with this range and that accuracy at least" and the compiler will chose the most efficient representation. In Smalltalk, one must define all the operations on lists. Ada choses efficient reps but is statically typed, and Smalltalk does not chose efficient reps but is dynamically typed. -- Darren -- --- Darren New --- Grad Student --- CIS --- Univ. of Delaware --- ----- Network Protocols, Graphics, Programming Languages, FDTs ----- +=+=+ My time is very valuable, but unfortunately only to me +=+=+ + When you drive screws with a hammer, screwdrivers are unrecognisable +
cs450a03@uc780.umd.edu (04/09/91)
Victor Yodaiken writes: >There seems no reason why we cant have a "static" programming >language in which type declarations are more flexible than current >definitions. Thus, if I declare "x,y" to range over numbers, then >"x+y, x*y " are well defined, but "x/y" is not, because the semantics >of / depends on more than the numberness of the arguments -- i.e. >integer/integer -> integer, float/float -> float, integer/integer >->(q,r) are all different functions. In general, you need to be able to do more with a computer than add and multiply :-) Consider a function Lookup(symbol_table, symbol) -- what should the type of the result be? Or consider Set_value(symbol_table, symbol, value) what type should "value" be? Could you imagine a symbolic math package which had to invoke a compiler at each stage to construct "statically typed code" appropriate to an expression? How about developing such a package in a language where you had to change declarations each time you wanted to work on a different class of problem? ----------------------------------- Consider the problem of function-rewriting. Let's say you have some function F(g(x), g(y)). Let's say F and g are both O(n squared), but that F(g, g) has properties that allow it to be computed in O(n). In a language where a significant part of the code is spent declaring how x and y (and presumably the results of F and g) are stored, how is a compiler supposed to be able to determine that it is appropriate to re-write this usage of F? I tend to think of static typing in the same light as side-effect driven programming. Both have "non-local" properties that make re-writing code difficult. [If static typing is purely local, then you are not typing a variable, but a value. If that "variable" is a constant, then there may be no significant difference between static and dynamic typing.] Function-rewriting involves a number of issues besides dynamic/static typing, but that's what I've been spending my spare time on... ----------------------------------- Let me pose a "classic typing problem": F(x) and f(x) are defined for some domain D -> D, but are not one-to-one G(x) is an inverse to F(x) for some values in D g(x) is an inverse to f(x) for some values in D F, f, G and g are all implemented as user defined functions on some computer system. All are pure computation (no side-effects). What type must x be for G(g(x)) to be meaningful? An easy way to deal with this problem is issue "message not understood" where x is invalid. Note that significant computation (g) may occur before this situation is recognized. Sometimes it may be recognized that if x is limited to some sub-domain d, then G(g(x)) will always be meaningful. Sometimes the computer might recognize this, sometimes the programmer might. Sometimes the problem is too hard. Raul Rockwell
olson@lear.juliet.ll.mit.edu ( Steve Olson) (04/09/91)
In article <28937@dime.cs.umass.edu> yodaiken@chelm.cs.umass.edu (victor yodaiken) writes: In article <OLSON.91Apr6155734@lear.juliet.ll.mit.edu> olson@lear.juliet.ll.mit.edu ( Steve Olson) writes: > >In article <28924@dime.cs.umass.edu> yodaiken@chelm.cs.umass.edu (victor yodaiken) writes: > The problem with "untyped" expressions is that they are inherently > ambiguous. If I'm writing about semigroups and regular languages I must > tell you when a*b is concatenation and when it is semigroup addition. > Figuring this out from the context alone is difficult for most people. > >This sounds more like overuse of operator overloading rather than anything >having to do with static vs. dynamic typing. This problem could occur just as >easily in C++ as in CLOS. If you never want to mix the two types then >renaming the operators will remove all confusion. If you do want to mix the >two types then the problem is the same whether you are using a dynamically >typed language or a statically typed sortof-dynamic-typing-through-subclassing- >and-virtual-functions language. I don't want to have to give up overloading -- one of the real conveniences of mathematical notation. But if I declare variable types, there is no problem. So, if I write: let a,b be elements of the alphabet and let a',b' be the corresponding elements of the semigroup, then a*b and a'*b' are easy to distinguish. By making the types of the variables explicit, I make the overloading of * unambiguous. An expression a*b' where the two variables have different types makes no sense in this context and I'd like the compiler to catch it. You know, my line about renaming the operators was a little weak; let me try to do better. First of all, you may want to mix the two, not in your core routines, but in support code that manipulates container classes. This is the old heterogeneous list problem, already hotly discussed on this list. Most solutions to this problem from the static typers are based on subclassing and virtual functions, which, as I orginally noted, lands you back where you started. Second, using a type system to either provide documentation or to catch errors (you arguments seem to center around this) is overrated. Just how often do you mistakenly confuse variables of totally different types? Everybody's experience is different, but for me the answer is virtually never. My viewpoint is that static typing *creates* the potential for type errors by demanding all sorts of diddly-squat decisions about is this number a int,float,double, or whatever. This can lead to efficiency gains, but it seems like a far cry from the concept of mathamatical type you have been talking about in other posts. I am aware that demanding detailed decisions about storage layout or machine data-types is not inherent to the concept of static typeing. I would be interested in hearing about a language where you could do something like declare x to be a "number". Does such exist? Efficiency aside, does there exist a language where a static type system catches lots of errors but dosen't get in your way the way current examples do? I don't know myself -- thats why I read this list! - Steve Olson MIT Lincoln Laboratory olson@juliet.ll.mit.edu
brm@neon.Stanford.EDU (Brian R. Murphy) (04/09/91)
In article <boehm.670539603@siria> boehm@parc.xerox.com (Hans Boehm) writes: >brm@neon.Stanford.EDU (Brian R. Murphy) writes: >>Unfortunately, given a statically-typed language with higher-order >>functions and an "all" type, type inference appears to be >>undecideable. Thus your statically-typed language _requires_ type >>declarations, whereas in a dynamically-typed language we can get by >>without them. > > I think this is getting into an area where we need a bit more precision. >The argument implied by Brian seems to be that with a dynamically typed >language and some automatic type inference, we can get similar performance >to a statically typed language. I think we are no longer addressing >reliability issues. I didn't say anything at all about performance. I would agree that with a statically typed language you can achieve higher performance than with a dynamic language, in general. Doing certain sorts of type inference can reduce the performance differential, but it's still true that statically typed programs will be more efficient in general. My complaint about statically typed languages is that I _can't_ do some things in them that I _do_ in dynamically typed languages (such as Lisp). For example, I can't I write a function which returns either a boolean or an integer in a complex way. I can't write my own Y combinator. I can't write a function which allows either a sequence of functions which range over numbers or a sequence of numbers as an argument. Let's consider what a type system might do for me: (1) It constrains the behavior of primitive functions so the program doesn't do undefined things. If "+" when applied to non-numbers has some undefined behavior, then such applications should be prevented, or else debugging programs becomes a nightmare. Any strongly typed language does this. [ This is essential, in my opinion, in either kind of type system. ] (1') In the case of overloaded primitives, a type system selects which underlying operation to use on given arguments. A static type system allows this to be done at compile time, a dynamic type system does it at run time, although analysis can do some at compile time. [ Static wins here. ] (2) It constrains the use of procedures that I write so that they aren't applied to things they weren't intended to apply to. Thus, I might declare an argument to have a particular type, and applications to objects not in that type are prevented. Statically typed languages usually _force_ me to make such declarations. The problem with this is that often a static type language is neither specific nor general enough to adequately describe the types in my programs. If I really want true safety here, I'd have to have a type language which allows me to express complex relationships (such as "sorted sequence", for example). Thus it must be very specific. In other cases, argument types (or certain parts of them) don't matter at all, and I'd like to be able to omit such declarations. Thus I want a type system which (a) allows me to omit many type declarations (where unnecessary) (b) allows me to be very specific in constraining some arguments With dynamic typing, I can simply write predicates to constrain arguments/variables (Common Lisp, FL, some implementations of Scheme do this). You static typing advocates claim that a static type system can do this for me, but I claim that it can't. A type language powerful enough to constrain arguments anywhere near as precisely as I need won't allow me to omit many types. Type inference is only possible for a limited class of type languages, and they tend to be fairly weak. In addition, certain programs are forbidden simply because they utilize types which can't be described by the type language used. [ dynamic wins here ] (3) It shows efficient representations of certain objects. A static type system allows this. Given sophisticated analyses, a dynamic type system allows this in some cases. [ static wins here ] IN Conclusion, I observe that static typing wins on points (1) and (3), which are merely performance issues. Dynamic typing wins big on point (2), which is a programming expressiveness issue. Neither is really adequate, but dynamic typing's problems are merely performance issues; improved compiler technology will gradually eat away the performance advantages of static typing (plus you can always just buy a faster machine). Static typing's problems seem a little harder to work around (required declarations, some valid programs forbidden, constraints on function use overly restrictive/unrestrictive), and trip up the way I write programs, by forcing me to do certain things in some ways. Thus, although I regret the loss of performance, I feel that dynamic typing is necessary and we should make the best of it. I'll observe one final point, which is that static type systems which satisfy point (2) generally allow such constraints to be resolved at compile time. This might reduce debugging time, but the expressible constraints in such systems aren't really powerful enough to provide any guarantee of program correctness, so thorough testing is still necessary. Thus I don't buy the argument that dynamically-typed languages aren't adequate for production code; the user won't get a run-time error message if you debugged your code well. Even if he does, better that than undefined behavior from a statically typed program whose producer put too much confidence in the type system to catch his errors. -Brian Murphy brm@cs.stanford.edu
augustss@cs.chalmers.se (Lennart Augustsson) (04/09/91)
In article <1593@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >OK, try this. In mathematics you don't have to describe the types of >objects unless the type is important. In statically typed languages, >you have to declare the type whether it has any relevance or not (or >fix it at compile time whether it is really knowable or not). In >mathematics or dynamically typed languages you can describe the >concept of a set or a sequence, and operations over those data >structures, without refering to the "type" of elements. For example >in math you could say > > If f is a function and s is a sequence, then map(f,s) is the > sequence t such that for all i . t[i] = f(s[i]). > >In a Icon (a dynamically typed language) you could define > > procedure map(f,s) > local t,i > t := list(*s) > every i := 1 to *s do t[i] := f(s[i]) > return t > end > >Try to write this function in a statically typed language so that it >has all the generality of the math and Icon versions. (Actually, the >math version works for infinite sequences and the Icon one doesn't. >There are languages that fix that...) No, forget the challenge. Sorry, I can't forget it. I'll just have to make another plug for polymorhic type deduction. Here's a version of map in Haskell (ML would be very similar): map f [] = [] map f (x:xs) = f x : map f xs This also handles infinite lists (of course). Haskell is statically typed, and from the definition of map the compiler deduces (i.e. you don't have to write it, but you can if you like): map :: (a->b) -> [a] -> [b] Just because some statically typed languages requires you to declare the type whether it has any relevance or not does not mean that this is true for all of them. -- Lennart Augustsson [This signature is intentionally left blank.]
mathew@mantis.co.uk (mathew) (04/09/91)
new@ee.udel.edu (Darren New) writes: > I don't think it has anything to do with machine representations or > efficiency. Your definition would exclude Objective-C, Smalltalk, C++, > and every other dynamically typed language I know except APL. When did C++ become dynamically typed? mathew -- If you're a John Foxx fan, please mail me!
mpj@prg.ox.ac.uk (Mark Jones) (04/09/91)
In article <1593@optima.cs.arizona.edu> David Gudeman writes: | OK, try this. | [description of a map function which applies a given function to each | element of a sequence ... includes both a mathematical version and | an Icon version.] | |Try to write this function in a statically typed language so that it |has all the generality of the math and Icon versions. Well Haskell is a statically typed language and the map function can be defined very neatly: map f xs = [ f x | x<-xs ] That's it. No need for any type declarations (although Haskell does infer that map has type (a->b) -> [a] -> [b] for any types a and b). As far as satisfying the mathematical definition that you gave: | If f is a function and s is a sequence, then map(f,s) is the | sequence t such that for all i . t[i] = f(s[i]). In Haskell, the ith element of a sequence xs is written xs!!i. So your condition can be written as: (map f xs)!!i = f (xs!!i). This property can be proved by simple structural induction (you will need to make the assumption that xs has an ith element (i.e. that xs!!i exists); a point which is implicit in your definition). In other words, the Haskell definitions give you precisely the mathematical behaviour that you've asked for! | (Actually, the |math version works for infinite sequences and the Icon one doesn't. |There are languages that fix that...) Haskell is one of them. | In statically typed programming |languages the purpose of the types is to let the compiler generate |better code, and the nature of the declarations reflects that. In Haskell, you don't usually need to give any type declarations ... perhaps some clever compilers will be able to use type declarations to generate better code ... but from my point of view, the main uses of type declarations are: - documentation ... just knowing the type of a function can give you a lot of information about it. - consistency checking ... if I choose to give an explicit type declaration, that reflects my intention as a programmer about how an object will behave. The Haskell type system will let me know if the definition of the object does not agree with my intentions. | For |example, you cannot simply declare something as a "number", you have |to decide whether you want it represented in floating point format or |integer, and what size you want. You can do this in (.. you guessed ..) Haskell. In fact, even numeric constants are treated as objects of type Num a => a meaning any type a so long as a is a numeric type (which might be integers, floating points, complex numbers ... even polynomials if you wanted). Haskell takes this further ... in an expression of the form x+y one does not have to insist that x,y are both Integers/Floats etc. All that is necessary is that they both have the same type a, and that a is a numeric type. This posting was not meant to be an advert for Haskell, but while I'm on the subject, how would you use your Icon program to add one to a list of lists of integers nss? I'd be surprised if you could do it more naturally than Haskell: map (map succ) nss where succ n = n+1 Which brings me onto a final point; if I can solve a problem P more naturally in a language L1 than I can in a language L2, does that mean that L1 is better than L2 (or that the type system of L1 is better than that of L2)? I certainly wouldn't want to claim superiority of Haskell over Icon (or anything else) in that way. [Best quit now before we get back to first-class functions...] Mark
mhcoffin@tolstoy.waterloo.edu (Michael Coffin) (04/10/91)
In article <1991Apr9.110217.10963@mathrt0.math.chalmers.se> augustss@cs.chalmers.se (Lennart Augustsson) writes: >... I'll just have to make another plug for >polymorhic type deduction. Here's a version of map in Haskell (ML >would be very similar): > > map f [] = [] > map f (x:xs) = f x : map f xs > >This also handles infinite lists (of course). Not to put down Haskell, but in some ways this isn't nearly as powerful as the Icon version. Icon lists aren't constrained to be homogenous, as Haskell lists are. So while "map" works for any Haskell list, it only manages to do so by putting artificial constraints on what lists can contain. -mike
nick@cs.edinburgh.ac.uk (Nick Rothwell) (04/10/91)
In article <1593@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David Gudeman) writes: > In a Icon (a dynamically typed language) you could define > > procedure map(f,s) > local t,i > t := list(*s) > every i := 1 to *s do t[i] := f(s[i]) > return t > end > > Try to write this function in a statically typed language so that it > has all the generality of the math and Icon versions. (me) - fun map(f, s) = case s of x :: y => f x :: map(f, y) | nil => nil; (ml) > val map = fn : ('a -> 'b) * 'a list -> 'b list Do I get a prize? > No, forget the challenge. > Someone is sure to post a solution (or near solution) using some > baroque system of static declarations from Ada or some wierd C trick > (I can think of one...). Not me, however. Nick. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "I see what you see: Nurse Bibs on a rubber horse."
gudeman@cs.arizona.edu (David Gudeman) (04/11/91)
In article <1991Apr9.110217.10963@mathrt0.math.chalmers.se> Lennart Augustsson writes: ]In article <1593@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]> If f is a function and s is a sequence, then map(f,s) is the ]> sequence t such that for all i . t[i] = f(s[i]). ]> ]>In a Icon (a dynamically typed language) you could define... ]> ]>Try to write this function in a statically typed language so that it ]>has all the generality of the math and Icon versions. ]Sorry, I can't forget it. I'll just have to make another plug for ]polymorhic type deduction. Here's a version of map in Haskell (ML ]would be very similar): ] ] map f [] = [] ] map f (x:xs) = f x : map f xs I'm getting really tired of pointing this out: the program above does not have the full generality of the mathematical or the dynamically typed version. This is a point I've made several times on several Haskell and ML programs, and I wish people would get the idea so I could stop repeating myself. The statically typed program only works on structures in which all elements have the same type -- and only when the compiler can infer that type. Type inference has some nice features, but it does _not_ give you the expressive power of dynamic typing. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
mauls@warwick.ac.uk (The Chief Slime Monster) (04/11/91)
In article <8687@skye.cs.ed.ac.uk> nick@lfcs.ed.ac.uk writes: > >(me) - fun map(f, s) = > case s of x :: y => f x :: map(f, y) > | nil => nil; >(ml) > val map = fn : ('a -> 'b) * 'a list -> 'b list > >Do I get a prize? > No, but ML does.
brian@comp.vuw.ac.nz (Brian Boutel) (04/11/91)
In article <1707@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David Gudeman) writes: |> In article <1991Apr9.110217.10963@mathrt0.math.chalmers.se> Lennart |> Augustsson writes: |> ] map f [] = [] |> ] map f (x:xs) = f x : map f xs |> |> I'm getting really tired of pointing this out: the program above |> does |> not have the full generality of the mathematical or the dynamically |> typed version. This is a point I've made several times on several |> Haskell and ML programs, and I wish people would get the idea so I |> could stop repeating myself. The statically typed program only |> works |> on structures in which all elements have the same type -- and only |> when the compiler can infer that type. |> |> Type inference has some nice features, but it does _not_ give you |> the |> expressive power of dynamic typing. Let us suppose that we apply a function f to a list in which not all elements have the same type, and let us suppose further that by some stroke of luck all the elements of the list "understand" something with the name f, so in mapping f through the list there is no exception or failure, and we get a new list. What useful operations can we then perform on the result list? We know nothing about its members, except that they all can all be the result of applying a function called f to something or other, which is not very helpful. Even if these values can tell you their own types, you can't write code to deal with all possibilities unless you know in advance what types can occur, in which case you can use a statically typed language, declaring a type which is a discriminated union of the possible types in the list. Any problem solution can be programmed in a statically typed language, if the programmer is prepared to *design* the program before writing it. I would be far more confident trusting my life/safety/money to such a program than to some piece of hackery written in a language where "expressive power" is more important than solid engineering principle. --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000 Fax: +64 4 712070
cs450a03@uc780.umd.edu (04/11/91)
Brian Boutel writes: >Let us suppose that we apply a function f to a list in which not all >elements have the same type, ... We know nothing about its members, >except that they all can all be the result of ... f ... > >Even if these values can tell you their own types, you can't write >code to deal with all possibilities unless you know in advance what >types can occur, in which case you can use a statically typed >language, ... > >Any problem solution can be programmed in a statically typed >language, ... union ... [then complains about run-time errors] Eh...? You might as well say that any dynamically typed language is a statically typed language. My experience is that I can develop a program about 6 times as fast in a dynamically typed language (APL in my case) than I can in a statically typed language (C in my case--I'd be even slower in FORTRAN). This is anecdotal, but you've chosen to ignore dozens of postings giving reasons why this might be so. I've seen postings which indicate other dynamically typed languages (Icon, Smalltalk) have similar advantages. Incidentally, I'd have to say C takes the cake as having the WORST run-time-error behavior of any language I've seen (besides machine language/assembly language). Please don't confuse the sort of things C does with its "dynamic types" with the sort of things that happen in a "true" dynamically typed language. Raul Rockwell
nick@cs.edinburgh.ac.uk (Nick Rothwell) (04/11/91)
In article <1707@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David Gudeman) writes: > This is a point I've made several times on several > Haskell and ML programs, and I wish people would get the idea so I > could stop repeating myself. Sorry. I agree with your point, but your mention of maths confused me. You said > If f is a function and s is a sequence, then map(f,s) is the > sequence t such that for all i . t[i] = f(s[i]). Now, using such general terms as "function" and "sequence" in a mathematical context, I assumed your were implying a homogeneous list. If you're assuming heterogeneous sequences, then the kind of mathematics you're dealing with gets more complicated than I assumed. I think. If you're into the world of heterogeneous types, then you have to address the situation where the function does something which makes no sense for one of the elements (I agree that applying lambda x. (x :: nil) will work for heterogeneous lists with more generality than ML or Haskell can typecheck) - but then, as I say, the mathematics is getting complicated. But, I dropped maths more than ten years ago... > The statically typed program only works > on structures in which all elements have the same type -- and only > when the compiler can infer that type. Er, the last sentence isn't true (if I were to nit-pick...). Given the ML/Haskell definition of map, I can then write - fun double list = map (lambda x. (x, x)) list to turn a list into a list of pairs. The compiler still doesn't know the type of the list (beyond a general polymorphic type) but it'll work anyway. The list is, of course, homogeneous. > Type inference has some nice features, but it does _not_ give you the > expressive power of dynamic typing. Fair enough. I choose the benefits of static typing (no runtime errors, plus the ability to build specifications, interfaces and datatypes) and find the expressive power denied to me to be no handicap. I have no problem with other peoples' views, applications, whatever. So. End of thread, right? :-) Nick. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "I see what you see: Nurse Bibs on a rubber horse."
cs450a03@uc780.umd.edu (04/12/91)
Nick Rothwell writes: >Fair enough. I choose the benefits of static typing (no runtime errors, ^^^^^^^^^^^^^^^^^ Seriously? How is this accomplished? Raul Rockwell
olson@lear.juliet.ll.mit.edu ( Steve Olson) (04/12/91)
In article <1991Apr11.053440.13401@comp.vuw.ac.nz> brian@comp.vuw.ac.nz (Brian Boutel) writes:
Let us suppose that we apply a function f to a list in which not all
elements have the same type, and let us suppose further that by some
stroke of luck all the elements of the list "understand" something with
^^^^^^^^^^^^^^
the name f, so in mapping f through the list there is no exception or
failure, and we get a new list. What useful operations can we then
perform on the result list? We know nothing about its members, except
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
that they all can all be the result of applying a function called f to
something or other, which is not very helpful.
Thats ridiculous. Your argument assumes a bad design. No language outlaws
stupidity. Do you believe that Lisp programmers go around making up lists
of completely random elements just to make things challanging?
Consider f=differentiate-symbolic-expression, f=print (the result
being the screen spaced used), f=insert-in-symbol-table, f=eval,
f=window-system-operation, f=describe-object, f=size-of,
f=database-query-pattern-match, ....
Even if these values can tell you their own types, you can't write code
to deal with all possibilities unless you know in advance what types can
occur, in which case you can use a statically typed language, declaring
a type which is a discriminated union of the possible types in the
list.
Of course you can use a statically typed language. The question is how much
programmer resources (usually more) vs. how much machine resources (usually
less) will the static solution consume? The choice depends on the application.
Any problem solution can be programmed in a statically typed language,
if the programmer is prepared to *design* the program before writing it.
Any problem solution can be programmed in a statically typed language, period.
So? Are you trying to imply that using a dynamically typed language somehow
prevents one from designing?
I would be far more confident trusting my life/safety/money to such a
program than to some piece of hackery written in a language where
"expressive power" is more important than solid engineering principle.
Dynamic typing implies programs are a "piece of hackery"? Expressive power
is somehow incompatable with solid engineering principle? Many (most?)
large complex programs written in statically typed languages have little
bits of roll-your-own dynamic typing in them. This is a prime source of
"hackery". Screw up one of those explicit type tags and there is no
end to the troubles you face.
--brian
- Steve Olson
MIT Lincoln Laboratory
olson@juliet.ll.mit.edu
brian@comp.vuw.ac.nz (Brian Boutel) (04/12/91)
In article <11APR91.08052192@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: |> |> My experience is that I can develop a program about 6 times as fast in |> a dynamically typed language (APL in my case) than I can in a |> statically typed language (C in my case--I'd be even slower in |> FORTRAN). This is anecdotal, but you've chosen to ignore dozens of |> postings giving reasons why this might be so. I've seen postings |> which indicate other dynamically typed languages (Icon, Smalltalk) |> have similar advantages. |> You might be familiar with Fred Brookes' book "The Mythical Man Month". In this he says Productivity seems constant in terms of elementary statements,... Programming productivity may be increased by as much as five times when a suitable high-level language is used So APL would be expected to lead to greater productivity than C because APL programs are shorter than C programs. And this is not do do with the absence of declarations. Anyway, static typing does not imply a requirement to declare everything. Besides, my concern is with reliability, safety, not how fast you can hack something together. Let's talk about the software engineering aspects of this topic. |> Incidentally, I'd have to say C takes the cake as having the WORST |> run-time-error behavior of any language I've seen (besides machine |> language/assembly language). Please don't confuse the sort of things |> C does with its "dynamic types" with the sort of things that happen in |> a "true" dynamically typed language. |> Did I even mention C? --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000 Fax: +64 4 712070
cs450a03@uc780.umd.edu (04/12/91)
Brian Boutel > Me >| >| My experience is that I can develop a program about 6 times as fast >| in a dynamically typed language ... [and more anecdotal stuff] >You might be familiar with Fred Brookes' book "The Mythical Man Month". >In this he says > Productivity seems constant in terms of elementary > statements,... Programming productivity may be increased by > as much as five times when a suitable high-level language is > used >So APL would be expected to lead to greater productivity than C >because APL programs are shorter than C programs. And this is not do >do with the absence of declarations. Anyway, static typing does not >imply a requirement to declare everything. (1) Since when are declarations not elementary statements? (2) While static typing does not require that you declare everything, it does require declarations. (3) Static typing tends to make expensive the kind of generalization that I've seen in high-level languages. >Besides, my concern is with reliability, safety, not how fast you can >hack something together. Let's talk about the software engineering >aspects of this topic. >| Incidentally, I'd have to say C takes the cake as having the WORST >| run-time-error behavior of any language I've seen (besides machine >| language/assembly language). Please don't confuse the sort of >| things C does with its "dynamic types" with the sort of things that >| happen in a "true" dynamically typed language. >Did I even mention C? Wow, that was a fun talk about the software engineering aspects of this topic ;-) (And I left out FORTH again .. oops) But no, you (Brian Boutel) didn't mention C. (Or any other language.) A number of other people did though. Just for fun, the software engineering aspects of this topic: (1) runtime vs purely static type checking (2) local declarations (e.g. functions) vs nonlocal declarations (e.g. storage allocation for later operations). (3) degrees of abstraction (can we look at operation blarg() as pure computation, or must it have side effects and/or be effected by other side effects). You can carry any of these concepts to heights of absurdity which I don't care to contemplate... Raul Rockwell
sfk@otter.hpl.hp.com (Steve Knight) (04/13/91)
Nick Rothwell writes: > If you're into the world of heterogeneous types, then you have to address > the situation where the function does something which makes no sense for > one of the elements [...] Unless I misunderstand Nick, this seems to be nothing more complex than the business of dealing with partial functions. Steve
gudeman@cs.arizona.edu (David Gudeman) (04/15/91)
In article <8742@skye.cs.ed.ac.uk> Nick Rothwell writes:
]
]Now, using such general terms as "function" and "sequence" in a mathematical
]context, I assumed your were implying a homogeneous list. If you're assuming
]heterogeneous sequences, then the kind of mathematics you're dealing with
]gets more complicated than I assumed. I think.
Why would you assume that? Math makes no distinction between
homogenous and heterogeneous lists. What complication are you talking
about? It is trivial to define a heterogeneous list in math:
Let S be the sequence beginning with S[1] defined as follows:
if i is odd then S[i] = i
if i is even then S[i] = {i, i-1}
It is equally trivial to define a function to act on heterogenous
elements:
Let f x be defined as follows:
if x is an integer then x
else if x is a set then the min element of x
All of this can be programmed directly in a language with dynamic
typing and requires extra effort in a language with static typing.
My main criterion for the expresiveness of a language is "does it let
me say what I want to directly, instead of making me work around
limitations in the language?"
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
cs450a03@uc780.umd.edu (04/15/91)
Brian Boutel writes: >First, let me try to define what I mean ... >- A type is a set of operations. >- a value has type T if it understands all the operations of type T Thus a value can have multiple types? Note that 0, 1, 33, -200 and 2000000000 might all share some types in common, yet not share other types. >- a type violation occurs when there is an attempt to apply to a value an > operation which is not in its type. At run-time, although some values may be computed by the compiler. >- Languages can be classified as untyped, dynamic typed, static typed. >- Untyped means that it is solely the programmer's responsibility to >ensure that no type violation occurs. Failure to ensure this will not >be detected at compile time or by the system at run-time. ... I'm a little foggy here... If a language has some run-time checking, and some compile time checking, and doesn't catch some things, how would you classify it. [e.g. C on a machine that has some arithmetic exception hardware.] >- Dynamic typed means that values/objects contain type information at >run-time, so can detect attempted type violations and respond. This >is fine unless the response is unanticipated. The response is unanticipated? I presume you mean when the programmer did not intend to apply a particular function to some particular datum? >- Static typed means that a type is associated at compile time with >each variable. (In a functional language, which is what interests me, >this normally means with each defined function and by implication its >formal parameters.) It is possible at compile time to ensure that a >value is never bound to a variable of the wrong type, and that only >type-valid operations are performed on variables, thus preventing >type violations. How is this possible? Especially in a machine with finite limits? Note that arithmetic overflow means that addition can produce values which don't have the type for addition. You can't use subtraction to generate a value for a function which does not accept 0, or negative numbers, unless static analysis of the code reveals that this is ok. Multiplication hits the overflow problem even harder. Etc. And that's just basic arithmetic. Raul Rockwell
cs450a03@uc780.umd.edu (04/15/91)
Brian Boutel writes: >The kind of languages I prefer, e.g. ML, Haskell, do not require >declarations of variables or functions. Types are inferred from the >context. (It may sometimes be pregmatically desirable to include some >declarations to limit the generality of deduced types for purposes of >run-time efficiency). Curious. Sounds to me like ML and Haskell are [at least partially] dynamically typed. [I presume by "do not require declarations" you mean "do not require declaration of 'type' or 'storage class'" -- I can't quite see you not having to declare any values :-) ] >I'm not objecting to dynamic typed languages, in fact I quite like >them, and occasionally use them. I do, however, react badly to some >of the claims made about their superiority, claims which ignore the >most important issues, and advocate use which, at the present time, >increases the risk to the public of computer systems. Maybe if I didn't use the phrase "dynamic type" ? I claim that a language that inserts run-time checks on the values which are applied to functions, and only removes these checks where static analysis of the code shows them to be redundant, is going to catch more errors than a language which does not. I claim that a language which allows a programmer to specify an operation concisely is going to result in better productivity (in terms of bug-free functionality produced) than a language which always requires the programmer to deal with trivial details of the implementation. If that means I'm arguing for ML, so be it :-) One of these days, I might just learn it and find out for myself. But note that I'm not argueing for any particular language -- more a class of languages, and a coding style. Do you react badly to that? Raul Rockwell
brian@comp.vuw.ac.nz (Brian Boutel) (04/15/91)
In article <OLSON.91Apr12012507@lear.juliet.ll.mit.edu>, olson@lear.juliet.ll.mit.edu ( Steve Olson) writes: |> [about something I wrote] |> Thats ridiculous. Your argument assumes a bad design. No language |> outlaws |> stupidity. Do you believe that Lisp programmers go around making up |> lists |> of completely random elements just to make things challanging? |> The ability to do just this has been touted by others as one of the advantages of dynamic typing. My point was that if they do not do this, they could use static typing, as you yourself say. |> Of course you can use a statically typed language. The question is how much |> programmer resources (usually more) vs. how much machine resources (usually |> less) will the static solution consume? The choice depends on the |> application. |> Again we agree -(see below) |>[me] Any problem solution can be programmed in a statically typed language, |> if the programmer is prepared to *design* the program before |> writing it. |> |> Any problem solution can be programmed in a statically typed |> language, period. |> So? Are you trying to imply that using a dynamically typed language |> somehow |> prevents one from designing? |> It's a strange kind of logic that has my claim imply what you suggest. There is, though a conection between the design effort I associated with using static types and the "programmer resources (usually more)" that you associate with static types. |> Many (most?) |> large complex programs written in statically typed languages have little |> bits of roll-your-own dynamic typing in them. This is a prime source of |> "hackery". Screw up one of those explicit type tags and there is no |> end to the troubles you face. This may well be true, but what does it show? A lack of design effort. Let me make a statement - up to now I have just responded to other postings. First, let me try to define what I mean (sorry if this is boring). - A type is a set of operations. - a value has type T if it understands all the operations of type T - a type violation occurs when there is an attempt to apply to a value an operation which is not in its type. - Languages can be classified as untyped, dynamic typed, static typed. - Untyped means that it is solely the programmer's responsibility to ensure that no type violation occurs. Failure to ensure this will not be detected at compile time or by the system at run-time. Usually the code implementing the operation is applied to the bit pattern representing the value as though that pattern represented a value of the appropriate type for the operation, with serious results. E.g. doing a floating point add to an integer value. - Dynamic typed means that values/objects contain type information at run-time, so can detect attempted type violations and respond. This is fine unless the response is unanticipated. - Static typed means that a type is associated at compile time with each variable. (In a functional language, which is what interests me, this normally means with each defined function and by implication its formal parameters.) It is possible at compile time to ensure that a value is never bound to a variable of the wrong type, and that only type-valid operations are performed on variables, thus preventing type violations. You made a trade-off between development costs and run-time costs. Fine, there is nothing wrong with that. I prefer to note the trade-off between development cost and run-time unreliability. I.e. it's better, in an important application, to take the extra time in development to protect against run-time type violations. This is not a complete guarantee of program correctness, but it is a step in the right direction. If I had to accept liability for damages resulting from the failure of a software system, which had to be developed from scratch, I would, other things being equal, (programmer skills and experience, etc), insist on the use of a static typed language. Since I moved from industry to Academia, my own programming efforts carry no such burden of responsibility, so I could freely use lisp or apl or smalltalk in my own little world, but I believe my responsibility to students, most of whom will become practitioners in industry, is to encourage them to use safe practices, or "solid engineering principle". --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000 Fax: +64 4 712070
brian@comp.vuw.ac.nz (Brian Boutel) (04/15/91)
In article <12APR91.08192346@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: [in response to something I said] |> |> (1) Since when are declarations not elementary statements? |> (2) While static typing does not require that you declare everything, |> it does require declarations. The kind of languages I prefer, e.g. ML, Haskell, do not require declarations of variables or functions. Types are inferred from the context. (It may sometimes be pregmatically desirable to include some declarations to limit the generality of deduced types for purposes of run-time efficiency). So there is really no difference between volumes of declarations required in these and in dynamic typed languages like Smalltalk. |> (3) Static typing tends to make expensive the kind of generalization |> that I've seen in high-level languages. |> I'm unsure what you mean. Are you saying that statically typed languages are not high-level? I would distinguish between "generalization" and "useful generalization". I'm sure you can show me some clever code, but do I need it at the cost of the additional safety that I get from using a language which disallows a large class of erroneous programs that might otherwise get into production. |> Just for fun, the software engineering aspects of this topic: |> |> (1) runtime vs purely static type checking |> (2) local declarations (e.g. functions) vs nonlocal declarations (e.g. |> storage allocation for later operations). |> (3) degrees of abstraction (can we look at operation blarg() as pure |> computation, or must it have side effects and/or be effected by |> other side effects). |> |> You can carry any of these concepts to heights of absurdity which I |> don't care to contemplate... |> |> Raul Rockwell Perhaps some more contemplation would help? Engineering is about reliability, about using established techniques with a solid underlying theoretical foundation, about safety margins. For me, these are the important issues. Nice new ideas have their place, but it is not in production systems. I'm not objecting to dynamic typed languages, in fact I quite like them, and occasionally use them. I do, however, react badly to some of the claims made about their superiority, claims which ignore the most important issues, and advocate use which, at the present time, increases the risk to the public of computer systems. --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000 Fax: +64 4 712070
nick@cs.edinburgh.ac.uk (Nick Rothwell) (04/15/91)
In article <11APR91.19025118@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: > Nick Rothwell writes: > >Fair enough. I choose the benefits of static typing (no runtime errors, > ^^^^^^^^^^^^^^^^^ > Seriously? How is this accomplished? Oh nuts, my fault for being too hasty. No runtime *type* errors. Runtime errors are restricted to a small set of exception conditions defined by the language. These can be caught and handled, and the exception mechanism allows you to define you own exception values and raise and handle them. I'm talking about ML here, btw. Nick. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "I see what you see: Nurse Bibs on a rubber horse."
anw@maths.nott.ac.uk (Dr A. N. Walker) (04/16/91)
In article <1366@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) replied rather peevishly to my <1991Mar29.191210.9369@maths.nott.ac.uk>: [On April 1st! Thanks to some time warp, DG's article has only just reached sunny Nottingham.] Well, you don't want to read it again. I'm happy to let readers decide which of us is the more confused about Algol and its implementation. One or two more substantive points: >]Algol 60: Intended primarily as a descriptive language, and certainly >] not efficient (call by name, dynamic typing > ------- >I assume you meant "static". You omitted to quote the "smiley" & the examples I gave. "2^j" is an integer if "j" is +ve, a real if it's -ve, so the type must be handled dynamically. In "f(2)", the compiler cannot tell, in general, whether "2" is a number or a label, so must provide enough information for the called routine to handle both [in real life, most (?all) actual implementations banned numeric labels]. More generally, a procedure which is passed as a parameter cannot have its parameters specified, so that uses of such parameters have dynamic type. >] recursive procedures, or dynamic arrays, > >Both features can be compiled. That is obvious with hindsight. I have a vague memory that recursive procedures only got into Algol 60 by oversight; on the last day, someone realised that they hadn't been *excluded*, and there was a furious debate with arguments about whether it was possible or useful, on the successful conclusion of which they were left in. Anyone know better, or have I just started an urban legend? In any case, dynamic *own* arrays are a pig to compile; again, they were [?universally] omitted from implementations. >I should have been more specific: concatenatable, sectionable strings >with automatic storage management. Those pathetic things that >statically typed languages call "strings" are really character arrays. Algol 68 is statically typed; its strings have all those properties. >Mathematicians had thought [graphs, eg] were useful for decades or centuries, Perhaps so, and there may have been lots going on at the research level, but graphs were not part of the normal armoury of the typical maths graduate or postgraduate in 1960. Even today, I would guess that most mathematicians who avoid the specialist pure or computing courses will learn rather little about graphs. I checked some of my textbooks [mostly 60's vintage]. I couldn't find a mention of graphs in *any* of the maths texts. I recall that in about 1974, a [reasonably distinguished] colleague was berated in public by his [rather more distinguished] head of department for wasting his time researching in graph theory -- "Time you did some *real* maths". I think that expecting graphs to appear in a computing language mainly intended for expressing numerical algorithms in 1960 is simply anachronistic. DG's assertion started out in an admirably succinct form, with two parts: (i) Algol is a low-level language, (ii) into which static typing was introduced as an efficiency hack. I can make sense of this, but strongly disagree with it. He now says that by Algol he really means "the design philosophy that resulted in Algol 60", and [as far as I can make out] that by efficiency he really means the frame of mind of "Type A designers" (concerned with efficiency, as opposed to type B expressivists); he adds > Thus [type A languages] needed >static typing, [...] >Algol 60 clearly and unambiguously fits into type A. It is true that >_within the type A camp_, they were somewhat cavalier about >efficiency, [...] But Algol 60 was designed expressly to be machine *in*dependent, in so far as its designers were able. People were fed up with having to invent a new high-level language for every new computer. The results may not have been as untainted with hardware considerations as DG would like, but the *intent* and the *philosophy* was high-level. And assertion (ii) now seems to read that static typing was introduced as an efficiency hack into a language that needed static typing by people who were cavalier, as far as languages that need static typing go, about efficiency. I can discern a dim glimmer of meaning behind this; but I just don't agree with it. Perhaps that's a good place to stop. -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
cs450a03@uc780.umd.edu (04/16/91)
Brian Boutel >|> or > Me <| >|>First, let me try to define what I mean ... >|>- A type is a set of operations. >|>- a value has type T if it understands all the operations of type T <| Thus a value can have multiple types? >Yes, within a type hierarchy, a value may have a type and that type's >supertype and so on upwards. I'm not sure if a "type hierarchy" is always appropriate. Consider the following functions: factorial, sine, reciprocal, log, addition, and multiplication. Describe a type hierarchy which allows me to use each of these functions (or operators, if you prefer) in a meaningful fashion, without arbitrary restrictions. I don't see how you can get away from type errors occuring at runtime. What I'm trying to say is that a general purpose function will quite likely be able to generate different values, which have different types. This occurs because the definition of "type" deals with what happens when a function is _applied_ to data. Type tags are implementation specific, and are not required to get run-time type errors. Maybe you're wondering what good it does it do to have a run-time error? Assuming you don't have some special case procedure to handle a specific error (like renormalization *gack* *gack* *cough* *gasp*), you could maybe back up to the last user input (or some generic user prompt), and suggest, wistfully, that the user trys something else. Much better than spewing out garbage, or crashing the program. >|>It is possible at compile time to ensure that a value is never >|>bound to a variable of the wrong type, and that only type-valid >|>operations are performed on variables, thus preventing type >|>violations. <| How is this possible? Especially in a machine with finite limits? <| Note that arithmetic overflow means that addition can produce values <| which don't have the type for addition. >This is the cause of the misunderstanding between us. I was not >including arithmetic exceptions such as overflow or divide by zero as >type violations. ... >So my type system has deficiencies, but it can still catch a lot of >errors at compile time that might otherwise get through testing. I >think that is worthwhile. I think you're on weak ground here. Essentially, you seem to be saying: subtle errors I don't consider, but blatantly obvious errors are worth catching. I also think that if you were working with a system which gave better support to run-time testing, you might catch more errors in testing. Finally, a statement which is formally accepted by the compiler is not necessarily a statement which is correct in the context of a specific program. If a language cuts the number of branches in half (by allowing the same statement to deal with more cases), _each_ branch removed cuts the testing time roughly in half (or by n, in the case of an n-way branch). A program which has 24 independent branches may be impossible to test adequately. A program with no branches can be almost trivial to test. I should mention that I spend much of my time tracking down and squashing lingering race conditions left by long-departed programmers. This kind of problem, in particular, seems to be inadequately addressed by static type checking (and I am relieved that I don't have to bother with it when I don't want to). Raul Rockwell
brian@comp.vuw.ac.nz (Brian Boutel) (04/16/91)
In article <15APR91.00191631@uc780.umd.edu>, cs450a03@uc780.umd.edu writes: |> Brian Boutel writes: |> |> >First, let me try to define what I mean ... |> >- A type is a set of operations. |> >- a value has type T if it understands all the operations of type T |> |> Thus a value can have multiple types? |> Yes, within a type hierarchy, a value may have a type and that type's supertype and so on upwards. |> |> >- Languages can be classified as untyped, dynamic typed, static typed... |> I'm a little foggy here... If a language has some run-time checking, |> and some compile time checking, and doesn't catch some things, how |> would you classify it. |> |> [e.g. C on a machine that has some arithmetic exception hardware.] |> I wuld classify C as statically typed, but see below. |> |> >- Static typed means that a type is associated at compile time with |> >each variable. (In a functional language, which is what interests me, |> >this normally means with each defined function and by implication its |> >formal parameters.) It is possible at compile time to ensure that a |> >value is never bound to a variable of the wrong type, and that only |> >type-valid operations are performed on variables, thus preventing |> >type violations. |> |> How is this possible? Especially in a machine with finite limits? |> Note that arithmetic overflow means that addition can produce values |> which don't have the type for addition. You can't use subtraction to |> generate a value for a function which does not accept 0, or negative |> numbers, unless static analysis of the code reveals that this is ok. |> Multiplication hits the overflow problem even harder. Etc. |> |> And that's just basic arithmetic. |> |> Raul Rockwell This is the cause of the misunderstanding between us. I was not including arithmetic exceptions such as overflow or divide by zero as type violations. In the type systems I am talking about, the type Integer supports the integer divide operation, (say, "div"), and the type of div is Integer X Integer -> Integer This does not take account of the fact that the type of div should really be Integer X NonZeroInteger -> Integer, where NonZeroInteger is a subtype of Integer. So my type system has deficiencies, but it can still catch a lot of errors at compile time that might otherwise get through testing. I think that is worthwhile. --brian -- Internet: brian@comp.vuw.ac.nz Postal: Brian Boutel, Computer Science Dept, Victoria University of Wellington, PO Box 600, Wellington, New Zealand Phone: +64 4 721000 Fax: +64 4 712070
gudeman@cs.arizona.edu (David Gudeman) (04/16/91)
In article <8872@skye.cs.ed.ac.uk> Nick Rothwell writes:
]
]No runtime *type* errors. Runtime errors are restricted to a small set of
]exception conditions defined by the language.
The only runtime error you have eliminated with static type checking
is a "message not understood" or "domain error". The elimination of
this single type of error hardly seems to justify the limitations on
expressiveness -- particularily since this is the easiest type of
error to find by testing.
The only reason you can kind find type errors at compile time is
because the errors are so trivial that the test is decidable. How
much are you willing to give up in expressiveness to find the most
trivial and obvious programming error a few minutes earlier?
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
wallace@hpdtczb.HP.COM (David Wallace) (04/16/91)
> brian@comp.vuw.ac.nz (Brian Boutel) / 9:34 pm Apr 10, 1991 / > Any problem solution can be programmed in a statically typed language, > if the programmer is prepared to *design* the program before writing it. > I would be far more confident trusting my life/safety/money to such a > program than to some piece of hackery written in a language where > "expressive power" is more important than solid engineering principle. I have heard there's a quote attributed to von Neumann to the effect that any competent programmer shouldn't need to use floating point - if you understand the problem well enough and *design* the program appropriately, you should be able to use appropriately scaled fixed point values throughout. (Someone with a better set of references than I might be able to trace the original quote. I've just heard it as oral folklore.) Anyway, programmers (and users) find floating point rather useful. Think of it as "dynamic scaling" vs. "static scaling." It may not be a perfect analogy, but it's worth thinking about. Sometimes not having to anticipate every detail up front can give you significantly more flexibility. (But if it's a life-critical application, I would still like the floating-point algorithms designed and/or checked by a competent numerical analyst. There are reliability issues associated with floating-point, too.) Dave W. (david_wallace@hpdtl.ctgsc.hp.com)
mathew@mantis.co.uk (mathew) (04/16/91)
cs450a03@uc780.umd.edu writes: > Curious. Sounds to me like ML and Haskell are [at least partially] > dynamically typed. [I presume by "do not require declarations" you > mean "do not require declaration of 'type' or 'storage class'" -- I > can't quite see you not having to declare any values :-) ] What ML actually does is to infer types from information given implicitly. For example, if you define a function f(x) = x + x, we can infer that f(x) is a function from some type 'a to a value of the same type 'a ( 'a -> 'a ), since we know that + is of type 'b * 'b -> 'b. If we then define a function g(y) = f(f(y)), we can infer that y must be of type 'a (in order to be a valid argument for f()) and hence that g(y) is a function 'a -> 'a. If we then do z = sin(g(0.2)), we can infer that type 'a must in fact be a floating-point number, since we know that sin is of type float -> float. Using such rules, ML cleverly deduces the type of functions without your ever having to explicitly state the type. It then does static compile-time type checking. (Apologies to ML experts if I've glossed over anything or made any silly errors; I haven't used ML in quite a while.) In order to allow this sort of deduction to take place, you must place certain limitations on what is allowed. For example, if you were to allow heterogeneous lists, you would be unable to deduce a single type for any list, leading to dead-ends in the type deduction. Sometimes it can be necessary to state a type explicitly, if there isn't enough information in the code for ML to deduce what you intend. It is always permissible to state the type explicitly if you wish to do so; although doing so removes most of ML's benefits. > I claim that a language that inserts run-time checks on the values > which are applied to functions, and only removes these checks where > static analysis of the code shows them to be redundant, is going to > catch more errors than a language which does not. Indeed. One of the most stupid things which people do is to remove all the run-time checking from released versions of programs, even when the programs are not time-critical. > I claim that a language which allows a programmer to specify an > operation concisely is going to result in better productivity (in > terms of bug-free functionality produced) than a language which always > requires the programmer to deal with trivial details of the > implementation. Agreed. I also think that a language which allows features such as (for example) heterogeneous lists is, in certain applications, going to result in enormously greater programmer productivity. Why use unions and implement your own type system using tags, when you can use a type system which is already there? If only we could solve the problem of generalized lambda-2 typing, we might be able to have a statically-typed language which was as powerful as Lisp... mathew -- If you're a John Foxx fan, please mail me!
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/16/91)
In article <1883@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <8742@skye.cs.ed.ac.uk> Nick Rothwell writes: >] >]Now, using such general terms as "function" and "sequence" in a mathematical >]context, I assumed your were implying a homogeneous list. If you're assuming >]heterogeneous sequences, then the kind of mathematics you're dealing with >]gets more complicated than I assumed. I think. > >Why would you assume that? Math makes no distinction between >homogenous and heterogeneous lists. What complication are you talking >about? It is trivial to define a heterogeneous list in math: > >Let S be the sequence beginning with S[1] defined as follows: > if i is odd then S[i] = i > if i is even then S[i] = {i, i-1} > >It is equally trivial to define a function to act on heterogenous >elements: > >Let f x be defined as follows: > if x is an integer then x > else if x is a set then the min element of x > >All of this can be programmed directly in a language with dynamic >typing and requires extra effort in a language with static typing. >My main criterion for the expresiveness of a language is "does it let >me say what I want to directly, instead of making me work around >limitations in the language?" >-- Your example seems to involve the use of type definitions. If I now write f((1,2,3,4)) the compiler should be able to inform me that I have attempted to apply "f" to a list, but "f" is only defined on sets and integers.
anw@maths.nott.ac.uk (Dr A. N. Walker) (04/16/91)
In article <1991Apr9.021700.2688@neon.Stanford.EDU> brm@neon.Stanford.EDU (Brian R. Murphy) writes: >My complaint about statically typed languages is that I _can't_ do >some things in them that I _do_ in dynamically typed languages (such >as Lisp). But your examples are perfectly OK in *some* statically typed languages, which suggests that it's a problem with your specific languages rather than with typing. > For example, I can't I write a function which returns >either a boolean or an integer in a complex way. proc fred = union (bool, int): if random < 0.5 then true else 1 fi; # eg, # print ("found a" + case fred in (bool): " bool" out "n int" esac) > I can't write my own >Y combinator. Pass. > I can't write a function which allows either a sequence >of functions which range over numbers or a sequence of numbers as an >argument. mode integer = union (int, long int), fraction = struct (integer numerator, denominator), number = union (integer, real, long real, fraction); proc (number) number bert = (skip); proc jim = (union ([] proc (number) number, [] number) a) void: (skip); # eg, # jim (1); jim (pi); jim ((1, pi)); jim ((bert, bert, bert)); # no guarantees that these will compile without casts! # What you are asking for is almost the standard "print" specification, proc print = ([] union (proc (ref file) void, int, real, etc)): ..., which is defined that way so that you can print arbitrary sequences of printable things, like arrays, strings, numbers, interleaved with procedures like "newline" that move around the output without printing. -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
yodaiken@chelm.cs.umass.edu (victor yodaiken) (04/16/91)
In article <OLSON.91Apr9012119@lear.juliet.ll.mit.edu> olson@lear.juliet.ll.mit.edu ( Steve Olson) writes: > >I am aware that demanding detailed decisions about storage layout or machine >data-types is not inherent to the concept of static typeing. I would be >interested in hearing about a language where you could do something >like declare x to be a "number". Does such exist? Efficiency aside, does >there exist a language where a static type system catches lots of errors but >dosen't get in your way the way current examples do? I don't know myself >-- thats why I read this list! Ignoring, for just a moment, the always fascinating topic of lisp versus C, this seems to get at what I want to understand from this discussion: namely, is there something inherent in "static" typing and type checking that makes it so tedious and baroque? Is it not possible to have a programming language in which types are used as in mathematics to clarify, disambiguate, and catch errors? I do not buy the argument that there is a real plus in being able to write expressions that may not have any meaning. That is, if I write $f(X)$ I should be able to ensure, during design time, that f is defined over all possible instantiations of X. Why, one would find it reasonable to do otherwise is beyond me. Sorry if this is just a result of ignorance. On the other hand, the clumsy type hierarchies of the programming languages that I am familiar with, are not that impressive either. It is not clear that "types" should form a "hierarchy" at all: integers are not simply a stripped down version of reals, the ring Z/3 is not just a smaller version of the ring Z/170, etc. etc.
dmg@ssc-vax (David M Geary) (04/17/91)
David Gudeman writes: ]In article <8872@skye.cs.ed.ac.uk> Nick Rothwell writes: ]] ]]No runtime *type* errors. Runtime errors are restricted to a small set of ]]exception conditions defined by the language. ]The only runtime error you have eliminated with static type checking ]is a "message not understood" or "domain error". The elimination of ]this single type of error hardly seems to justify the limitations on ]expressiveness -- particularily since this is the easiest type of ]error to find by testing. Yes, a statically typed language, of course, still leaves all kinds of holes for all kinds of bugs... ]The only reason you can kind find type errors at compile time is ]because the errors are so trivial that the test is decidable. How ]much are you willing to give up in expressiveness to find the most ]trivial and obvious programming error a few minutes earlier? What about the type error that only shows up under rare circumstances? It is quite possible that production software may contain some hidden type error that only shows up if the user performs action A, followed immediately by action B, and then immediately performs action A again. (OSTTE). This may or may *not* be caught by testing. However, with a statically (or dynamically) typed language, it is of course, possible to have hidden bugs that have nothing to do with type. Many C (or C++) programmers can be found who write code like this: void printXValue( someType *p) { printf("%d\n", p->x); } The code will type check, and thus get by the compiler if x is a valid member of the type someType. However, what if, under some strange circumstance, the function printXValue() is passed a NULL pointer? I am amazed by the number of people who insist that a language type check, but turn around and assume that their functions will never be passed a bad pointer. Writing code that *appears* to be type-safe to a compiler is only a small part of the whole issue of writing correct code. There is no question that a dynamically typed language increases the risk that an error may occur during runtime that the software is not prepared to handle. Of course, dynamically typed languages permit more generic (and therefore reusable) code. Thus, we have a tradeoff. Two questions then arise: 1) What is the possiblity that a type error will occur at runtime? 2) Is the expressiveness provided by a dynamically typed language worth the possiblity of a runtime type error?
new@ee.udel.edu (Darren New) (04/17/91)
In article <3857@ssc-bee.ssc-vax.UUCP> dmg@ssc-vax.UUCP (David M Geary) writes: >void printXValue( someType *p) >{ printf("%d\n", p->x);} > The code will type check, and thus get by the compiler if x is a valid >member of the type someType. Not even considering what happens if p->x is a long, or a double, or a pointer, or .... -- Darren -- --- Darren New --- Grad Student --- CIS --- Univ. of Delaware --- ----- Network Protocols, Graphics, Programming Languages, FDTs ----- +=+=+ My time is very valuable, but unfortunately only to me +=+=+ +=+ Nails work better than screws, when both are driven with screwdrivers +=+
gudeman@cs.arizona.edu (David Gudeman) (04/17/91)
In article <3857@ssc-bee.ssc-vax.UUCP> David M Geary writes:
]
] ... There is no question that
]a dynamically typed language increases the risk that an error may occur during
]runtime that the software is not prepared to handle.
There certainly is a question about that. I claim that dynamically
typed languages _reduce_ the risk that such an error may occur. And
I've been claiming that for a month now, but people seem to forget it
two replies later.
Every time I see something like "I use statically typed languages for
security..." I grit my teeth and heroically try not to hit the
follow-up key (with more-or-less success). No one has yet presented
anything resembling evidence that the above is a true statement.
My argument is that: (1) the type errors that are caught by static
typing are the easiest kind of errors to find by testing -- so static
type checking is of no real value for product security. And (2) the
complex declarations required by static typing can be sources of
hard-to-find errors. (Complex declarations does not mean the
requirement to declare the types of variables, but of structures and
generic functions.)
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
sfk@otter.hpl.hp.com (Steve Knight) (04/17/91)
Just to expand on Brian Boutel's remarks about statically typed languages such as ML, HOPE, and Haskell. He comments: > The kind of languages I prefer, e.g. ML, Haskell, do not require > declarations of variables or functions. Types are inferred from the > context. [...] So there is really no difference between volumes > of declarations required in these and in dynamic typed languages like > Smalltalk. These languages are completely statically typed & are probably amongst the best examples of statically typed languages. The "type inference" mechanism is guaranteed to find the most general type expression (with a few trivial caveats). Dynamic typing is not involved. However, this compromise is not achieved without a certain cost. In particular, all collections (tuples don't count) are homogenous. This is a fundamental constraint that allows the type inference mechanism to do its stuff. The result of this is that "anonymous" type unions aren't supported. So if you want to construct a list with an integer and a boolean in it you write (in ML) datatype IntOrBool = Int of int | Bool of bool; where "Int" and "Bool" are constructors invented by this declaration. We can then construct the list as val LIST = [Int 3, Bool true, Int 99]; As you can see, although we managed to create a list that roughly corresponds to our initial specification without any type declarations, we had to invent a new datatype instead. I call these type declarations "cosmetic" in that they mask the type differences of elements in a collection. These cosmetic type definitions are not as densely used as type declarations in other statically typed languages such as Pascal, Ada, C, or (to pick a sensible example) POLY. But they do proliferate in even relatively moderate applications. It should also be appreciated that type inferencing works well in functional programming languages, such as ML and Haskell, but is much less effective in general imperative languages. This is evident in ML, which has a handful of imperative features, where the type system becomes considerably less attrative and more complex. So programming in a language such as ML is not as comfortable as writing in LISP, SmallTalk, or Prolog. But it is certainly a great deal more comfortable than writing in the other statically typed languages. (I haven't used Haskell, so I can't comment on whether or not it is an improvement -- my expectation is that the "class" layer does provide real benefits -- but that's another story.) Please note that I use the word "comfortable". By this I am implying ease of use for a programmer rather than any other qualities. Steve
nick@cs.edinburgh.ac.uk (Nick Rothwell) (04/17/91)
In article <1957@optima.cs.arizona.edu>, gudeman@cs.arizona.edu (David Gudeman) writes: > In article <8872@skye.cs.ed.ac.uk> Nick Rothwell writes: > ] > ]No runtime *type* errors. Runtime errors are restricted to a small set of > ]exception conditions defined by the language. > > The only runtime error you have eliminated with static type checking > is a "message not understood" or "domain error". The elimination of > this single type of error hardly seems to justify the limitations on > expressiveness It does if that error accounts for a huge proportion of errors. > How > much are you willing to give up in expressiveness to find the most > trivial and obvious programming error a few minutes earlier? If it were always a case of a few minutes (and if the runtime system were as informative as the compiler when errors occured), then it wouldn't be worth it. But, runtime type errors are indeterminate since not every execution path is guaranteed to be followed within a fixed period of testing time. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "Playing strip poker with an exhibitionist somehow defeats the object...."
nick@cs.edinburgh.ac.uk (Nick Rothwell) (04/17/91)
In article <3857@ssc-bee.ssc-vax.UUCP>, dmg@ssc-vax (David M Geary) writes: > Many C (or C++) programmers can be found who write code like this: > > void printXValue( someType *p) > { > printf("%d\n", p->x); > } > The code will type check `printf' isn't typechecked. -- Nick Rothwell, Laboratory for Foundations of Computer Science, Edinburgh. nick@lfcs.ed.ac.uk <Atlantic Ocean>!mcsun!ukc!lfcs!nick ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ "Playing strip poker with an exhibitionist somehow defeats the object...."
wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) (04/17/91)
gudeman@cs.arizona.edu (David Gudeman) writes: >Every time I see something like "I use statically typed languages for >security..." I grit my teeth and heroically try not to hit the >follow-up key (with more-or-less success). No one has yet presented >anything resembling evidence that the above is a true statement. Since static or strong typing is a well established paradigm of software engineering I guess you are in the position to present some arguments of evidence against it, arent you? Anyway, you did ... >My argument is that: (1) the type errors that are caught by static >typing are the easiest kind of errors to find by testing -- so static >type checking is of no real value for product security. To repeat, what several people already pointed out: This depends strongly on the kind of application. I dont know what kind of application you are dealing with. I would suspect at least with the following ones you might get some serious troubles "trusting in testing": a) Real-time applications, e.g. network-protocols. Here holds the Heisenberg syndrome ... you cannot observe them objectively. b) Sophisticated interactive systems: you cannot imagine all the possible action-sequences the user might performe. c) Complex data transformations, e.g. optimising compilers: there are always some exotic combinations of input data you missed to take into account in your test suite. You claim, that the errors caught by static typing are trivial ones. This is usally right. However, my experience is that most bugs of the kind you are searching for days over days are finally of trivial nature. Logical errors are much more easy to recover; actually, most of them will be detected during the implementation phase. The principal point here is related to the paradigm of compositionality of software pieces. This claims it should be possible to compose correct pieces of software yielding a new correct piece of software with semantics formed from the semantics of the software pieces and the semantics of the composition operator. A trivial error in one piece under composition becomes a less trivial error in the composed result and so on. One way to avoid such errors is to force some kind of compatibility condition for the software pieces under composition which is checkable by a tool. The easiest and indeed most restrictive way for this is monomorphic typing as known from C, MODULA-2, etc. But there are more sophisticated models of typing already applicated (parameterization, subtyping) and even more sophisticated under research (dependent types, parameter constraints, etc.), which the fans of dynamic typing permantly ignore. >And (2) the >complex declarations required by static typing can be sources of >hard-to-find errors. (Complex declarations does not mean the >requirement to declare the types of variables, but of structures and >generic functions.) I cannot follow you here. Several people already pointed out that the requirement of type declarations is not connected with the requirement of strong or static typing (see ML, Haskell). Its right that especially in a language with type unification some raised type errors are hard to fix (ever get the message "Type error in RHS of equation" or how it was called in HOPE?). But this just shows that there are also non-trivial type errors. >-- > David Gudeman >gudeman@cs.arizona.edu >noao!arizona!gudeman -- Wolfgang Grieskamp wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
gudeman@cs.arizona.edu (David Gudeman) (04/18/91)
In article <29350@dime.cs.umass.edu> victor yodaiken writes:
]
]Your example seems to involve the use of type definitions. If I now write
]f((1,2,3,4)) the compiler should be able to inform me that I have
]attempted to apply "f" to a list, but "f" is only defined on sets and
]integers.
For any variable v in any context whether mathematical, programming,
or natural language, you can find some set V such that all the
possible values of v are in V. However, static typing implies more
than that: it implies that expressions are constrained statically such
a way that all expressions can be assigned types from a restricted
set. The restricted set is such that machine representations can be
decided on at compile time and such that no type information has to be
associated with a value at runtime.
My example cannot be implemented in any programming language without
someone -- either the programmer or the language implementer --
associating type information with the values of the sequence. If you
associate type information with a value at runtime then you have
dynamic typing.
My claim is not now, and never has been, that all static type checking
is bad, evil, or even rude. I am only saying that in situations such
as the above, it is the language implementator who should be taking
care of type tags rather than the programmer. When the implementator
does it, it leads to less code, less complexity, and fewer bugs.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (04/18/91)
In article <29352@dime.cs.umass.edu> victor yodaiken writes:
]... I do not
]buy the argument that there is a real plus in being able to write
]expressions that may not have any meaning.
If you can't write an expression that may not have any meaning then
your language is not expressive enough to write programs in.
] That is, if I write
]$f(X)$ I should be able to ensure, during design time, that
]f is defined over all possible instantiations of X. Why, one would
]find it reasonable to do otherwise is beyond me.
That is impossible in a Turing-complete language. All you can do is
make an arbitrary approximation to that kind of safety. And static
typing approximations (ones that allow the representation of all
values to be decided at compile time) are overly restrictive for the
amount of safety they provide. The fact that they are overly
restrictive is easy to see by how much effort is put into getting
around the restrictions.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) (04/18/91)
[LANGUAGE WARNING: You might going to see a lot of spelling errors, notion errors, style errors, etc.] brm@neon.Stanford.EDU (Brian R. Murphy) writes: >My complaint about statically typed languages is that I _can't_ do >some things in them that I _do_ in dynamically typed languages (such >as Lisp). For example, I can't I write a function which returns >either a boolean or an integer in a complex way. I can't write my own >Y combinator. I can't write a function which allows either a sequence >of functions which range over numbers or a sequence of numbers as an >argument. Your examples are quite abstract. Maybe its missing phantasy -- but i cannot imagine the use of a function which returns either a boolean or an integer unless there is some relationship between this values, e.g. the boolean is an error value and normally an integer is expected, in which case either subsorting (multiple supersorts, of course) or parameterization is sufficient. And i see no need to write your own Y combinator, unless you use a language for a ground course in lambda calculus. (BTW, you can model it in strongly typed functional languages using recursive data types; of course a bit cumbersome). >Let's consider what a type system might do for me: >(1) It constrains the behavior of primitive functions so the program >doesn't do undefined things. >[ ... ] >[ This is essential, in my opinion, in either kind of type system. ] >(2) It constrains the use of procedures that I write so that they >aren't applied to things they weren't intended to apply to. Thus, I >might declare an argument to have a particular type, and applications >to objects not in that type are prevented. I do not see the difference between primitive functions, in which case you regard type safety as essential, and user defined onces. > [Thus] I want a type system which > (a) allows me to omit many type declarations (where unnecessary) > (b) allows me to be very specific in constraining some arguments Yes, i'am too. >With dynamic typing, I can simply write predicates to constrain >arguments/variables (Common Lisp, FL, some implementations of Scheme >do this). ... but i regard it as an abuse of notion to call such dynamic calculated predicates types. You can model them just using conditionals and an error halt function. What Common Lisp gives you here is simply syntactic sugar in an essential untyped environment, and some (rather restricted) kind of partial evaluation of this predicates. >You static typing advocates claim that a static type system can do >this for me, but I claim that it can't. A type language powerful >enough to constrain arguments anywhere near as precisely as I need >won't allow me to omit many types. My experience is the other way around. It would allow me to omit many types, and in some rare cases, if have to add declarations. However, unless I'am going to hack a one-night program, i would always declare signatures at least for the exported functions of a module even if they could be inferred. This seems to me a reasonable kind of documentation. >Type inference is only possible >for a limited class of type languages, and they tend to be fairly >weak. In addition, certain programs are forbidden simply because they >utilize types which can't be described by the type language used. This is theoretically right. But the question is, how often instances outside this limited, weak class are required in practice? For example, tell me an instance of practical evidence for the need of self application! Whats irritating me more and more in this discussion (as long as I'am following it; the last week) is that the advocates of dynamic typing claim for more expressivness, which *looks* like the arguments of practical men, but in fact seems to be more of philosophical nature. (This would be quite alright, but say it like it is.) I mean, the arguments often cummulate in some existential view to software development: anything a man is able to do should him be allowed to do ... pull down restrictions since they are restrictive ... redundancy is for softys ... etc. > -Brian Murphy > brm@cs.stanford.edu -- Wolfgang Grieskamp wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) (04/18/91)
gudeman@cs.arizona.edu (David Gudeman) writes: > If you >associate type information with a value at runtime then you have >dynamic typing. I have heard of this definition before, but I think its to weak. For instance, to implement C++ virtual messages, you have to add type information to values. (If C++ was classified as a dynamical typed language in the course of this discussion, I must apologize.) To implement Haskells type classes (which is surely statically typed) you dont have to add type information to values, but at least to functions performing computations with these values. Its an interesting observation that the restrictions of Haskell which dont allow the usage of "heterogeneous" lists are caused to the fact that type information is associated only with values passed to functions but not with values stored in data types. This seems to be quite unnaturaly in a framework based on the lambda calculus, where data types can be expressed through higher-order functions. -- Wolfgang Grieskamp wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
cs450a03@uc780.umd.edu (04/18/91)
Victor Yodaiken > Me >> >B: Type declarations do not necessarily only have to do with how data >is stored. A type declaration: "z in Domain(g) is a number" or "z in >Range x is a binary tree" or "Domain of f is ordered under >" all >seem like useful type declarations, but they have nothing to do with >storage. Ok, let's take "z in Domain(g) is a number" as an example. What is the result of that statement? If you're dealing with static typing, then I suppose you would say that the compiler dutifully takes note of the statement and rejects usages of z which conflict with this declaration. But what if computing Domain(g) is a problem? Let's say z is an offset into a file. If you want this to be statically typed, you've just specified a fixed-sized file. >>Let me pose a "classic typing problem": >>F(x) and f(x) are defined for some domain D -> D, but are not one-to-one >>G(x) is an inverse to F(x) for some values in D >>g(x) is an inverse to f(x) for some values in D >>F, f, G and g are all implemented as user defined functions on some >>computer system. All are pure computation (no side-effects). >>What type must x be for G(g(x)) to be meaningful? >> >>An easy way to deal with this problem is issue "message not >>understood" where x is invalid. Note that significant computation (g) >>may occur before this situation is recognized. >> >>Sometimes it may be recognized that if x is limited to some sub-domain >>d, then G(g(x)) will always be meaningful. Sometimes the computer >>might recognize this, sometimes the programmer might. Sometimes the >>problem is too hard. >I don't believe that there is a good reason for writing programs with >non-deterministic behavior There is nothing non-deterministic in that problem. For example, let's say that G(y) is 1/p(y) where p(y) is 0 when y is a positive integer. Let's say g(x) always gives results which are less than 0 -- pretty easy to deal with, eh? But what if g(x) can give results which are positive integers? Raul Rockwell
olson@juliet.ll.mit.edu ( Steve Olson) (04/18/91)
In article <3086@opal.cs.tu-berlin.de> wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) writes:
Since static or strong typing is a well established paradigm of software
engineering I guess you are in the position to present some
arguments of evidence against it, arent you? Anyway, you did ...
Oops, not at all the same thing (didn't this come up a few weeks ago?).
In fact, most dynaically-typed languages (Lisp, Smalltalk) are strongly
typed. Statically typed languages vary in the "strength" of thier typing.
I do not keep up with the software engineering literature, but I would venture
a guess that strong typing is the well established paradigm, and static
typing is, well, what we are arguing over ...
You claim, that the errors caught by static typing are trivial ones.
This is usally right. However, my experience is that most bugs of the
kind you are searching for days over days are finally of trivial nature.
Um, you're overloading the word "trivial". Trivial as in "is not a
fundamental error in medium or large scale program design" and trivial as
in "easy to find". In a typical dynamicaly-typed language, type errors
tend to be trivial in both senses of the word. The trival (in the first
sense of the word) errors that cause me real problems are when I type "+"
when I meant "-", or I type "ymin" when I meant "xmin". Static typing won't
help there.
Static typing will help catch errors in areas of the a program that are
not exercised well by test runs. However I am dubious about the net positive
effect of this because todays typical statically-typed language requires
you to do bizzare things to get around the typing system - thus causing
type errors. I spotted the following in the the copy of SunExpert that
showed up in my mailbox today (p 31) -
char *rv;
rv = mmap( ..... );
if ( rv == (char *)-1) {
Hmm, rv is either a pointer or a flag value ... and you tell 'em apart at
run time ... dynamic typing! Except that you are an order of magnitude
more likely to make a hard-to-find error than something equivalent in a
dynamically typed language. If mmap really returns 0 as its flag value,
then its say hello to Mr. mystery core dump.
--
Wolfgang Grieskamp
wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
- Steve Olson
MIT Lincoln Laboratory
olson@juliet.ll.mit.edu
cs450a03@uc780.umd.edu (04/19/91)
Wolfgang Grieskamp posts: > Cardelli & Wegner: "On Understanding Types, Data Abstractions, and > Polymorphims" > > "Programming languages in which the type of every expression can be > determined by static program analysis are said to be statically > typed. ... [The] weaker requirement [is] that all expressions are > guaranteed to be type consistent although the type itself maybe > statically unknown; this can be generally done by adding some > run-time type checking [1]. Languages in which all expressions are > type consistent are called strongly typed languages. If a language > is strongly typed, its compiler can guarantee that the programs it > accepts will execute without type errors [2]." At first, I thought that this was a definition which is exactly the opposite of the terminology we've been using in this newsgroup. But the second time around, I decided that these are requirements for statically typed languages. This definition does not cover dynamically typed languages, and most definitely does not say anything about languages which are strongly dynamically typed. Furthermore, within the context of the above definition, I might have to say that C is a strongly statically typed language (depends on what the definition of a "type error" is in that book). Raul Rockwell
wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) (04/19/91)
olson@juliet.ll.mit.edu ( Steve Olson) writes: >In article <3086@opal.cs.tu-berlin.de> wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) writes: > Since static or strong typing is a well established paradigm of software > engineering I guess you are in the position to present some > arguments of evidence against it, arent you? Anyway, you did ... >Oops, not at all the same thing (didn't this come up a few weeks ago?). >In fact, most dynaically-typed languages (Lisp, Smalltalk) are strongly >typed. Statically typed languages vary in the "strength" of thier typing. >I do not keep up with the software engineering literature, but I would venture >a guess that strong typing is the well established paradigm, and static >typing is, well, what we are arguing over ... I dont't know the result of the result of the dispute some weeks ago, but since I began to feel insecure (somebody e-mailed claiming too that Lisp & Smalltalk are strongly typed) I looked up a reference: Cardelli & Wegner: "On Understanding Types, Data Abstractions, and Polymorphims" "Programming languages in which the type of every expression can be determined by static program analysis are said to be statically typed. ... [The] weaker requirement [is] that all expressions are guaranteed to be type consistent although the type itself maybe statically unknown; this can be generally done by adding some run-time type checking [1]. Languages in which all expressions are type consistent are called strongly typed languages. If a language is strongly typed, its compiler can guarantee that the programs it accepts will execute without type errors [2]." I interpret (1) that with a strongly typed language it might become necessary to perform type checks at run-time (and thus associate type information with values) in order to REALIZE THE SEMANTICS of a type construct, but not to check type errors. (2) clearly drops Lisp and Smalltalk out of the candidates for strongly typed languages. Thats the way I learned it, I dont know if other references bring up other definitions. :-) -- Wolfgang Grieskamp wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
cs450a03@uc780.umd.edu (04/19/91)
Dan Bernstein writes: >Exception handling is hardly dynamic typing. You mean because it doesn't require type tags? Or maybe because it has hardware support for some cases, on some machines? Maybe I'm not totally clear on the conceptual difference between checking for index-out-of-bounds on an array, and checking for arithmetic overflow on some arithmetic calculation? As near as I can figure, exception handling is a special case of dynamic typing. Raul Rockwell
brnstnd@kramden.acf.nyu.edu (Dan Bernstein) (04/19/91)
In article <OLSON.91Apr18021608@lear.juliet.ll.mit.edu> olson@juliet.ll.mit.edu ( Steve Olson) writes: > char *rv; > rv = mmap( ..... ); > if ( rv == (char *)-1) { > Hmm, rv is either a pointer or a flag value ... and you tell 'em apart at > run time ... dynamic typing! Exception handling is hardly dynamic typing. ---Dan
kers@hplb.hpl.hp.com (Chris Dollin) (04/22/91)
Here's a hetrogenous list: [[ widget % LabelWidget % name % name.as_string % translations % standard_translations % install % set_actions(% action %) % borderWidth % 1 % borderColor % 'black' % background % 'gray' % ]] The %-ed items are evaluated, the others (eg "widget") are not. This list is one of the arguments to a widget instantiator. If I wrote it in a statically typed language, I expect I'd have to (a) gather each name-value pair into a twople [*1] and (b) inject each of the values into a suitable union type. If anyone's really interested, I'll explain what it means. I'm happy to admit that type errors may occur at run-time here; in fact, I'll lay odds that many of those same type errors would occur in a statically-typed language (when projecting a value back out of the presumed union type). So we need a way of handling exceptions; but we knew *that* already. It would be bootless to tell me that I ought to have an interface to the instantiator that took (say) the widget as a separate argument, the colour(s) as others, etc; one of the points about this interface is that given a widget description, it can be modified (usually by appending stuff on the end) without instantiating it, or inspected. -- Regards, Kers. | "You're better off not dreaming of the things to come; Caravan: | Dreams are always ending far too soon."
brm@neon.Stanford.EDU (Brian R. Murphy) (04/23/91)
In article <1991Apr16.151100.7221@maths.nott.ac.uk> anw@maths.nott.ac.uk (Dr A. N. Walker) writes: >In article <1991Apr9.021700.2688@neon.Stanford.EDU> >brm@neon.Stanford.EDU (Brian R. Murphy) writes: > >>My complaint about statically typed languages is that I _can't_ do >>some things in them that I _do_ in dynamically typed languages (such >>as Lisp). > > But your examples are perfectly OK in *some* statically typed >languages, which suggests that it's a problem with your specific languages >rather than with typing. Your solutions to my examples all have type declarations in them. In my posting, I believe I clarified the my above complaint with "without type declarations." -Brian Murphy brm@cs.stanford.edu
brm@neon.Stanford.EDU (Brian R. Murphy) (04/23/91)
In article <3092@opal.cs.tu-berlin.de> wg@opal.cs.tu-berlin.de writes: >[LANGUAGE WARNING: You might going to see a lot of spelling errors, > notion errors, style errors, etc.] > >brm@neon.Stanford.EDU (Brian R. Murphy) writes: > > >My complaint about statically typed languages is that I _can't_ do > >some things in them that I _do_ in dynamically typed languages (such > >as Lisp). For example, I can't I write a function which returns > >either a boolean or an integer in a complex way. I can't write my own > >Y combinator. I can't write a function which allows either a sequence > >of functions which range over numbers or a sequence of numbers as an > >argument. > >Your examples are quite abstract. Maybe its missing phantasy >-- but i cannot imagine the use of a function which returns >either a boolean or an integer unless there is some relationship >between this values, e.g. the boolean is an error value and >normally an integer is expected, in which case either subsorting >(multiple supersorts, of course) or parameterization is sufficient. I fail to see how parameterization is relevant. But yes, the case of of a possible error value _is_ a more common case of my example. For example, a Lisp expression such as (if (= y 0) (error) (/ x y)) would have type [something like] (error + int). So either we must automatically include errors in every base type, introduce an explicit union declaration in just about every function, or complicate things a bit by going to a continuation-passing semantics. Other examples besides errors _do_ come up, though, so why not just adopt a more general solution? >And i see no need to write your own Y combinator, unless you use a >language for a ground course in lambda calculus. (BTW, you can >model it in strongly typed functional languages using recursive >data types; of course a bit cumbersome). Almost any sort of `meta-interpreter/compiler' (a la Abelson & Sussman) which constructs functions in a generalized way contains expressions with recursive function types. The Y combinator was just a particularly simple example. Such translators are very convenient to be able to write. >I do not see the difference between primitive functions, in which >case you regard type safety as essential, and user defined onces. Well, to facilitate debugging, the primitive functions have to behave in some defined way on _every_ input, preferably some sort of error signal/report to the user. No static type system I know of can eliminate all run-time errors (cf divide by 0, array index out of bounds, disk full, out of memory, etc), so there must be a run-time mechanism. Similarly, there must be some mechanism for a user-defined partial function to signal an error when given bad arguments. The language designer cannot possibly anticipate which domains a user-defined function should be defined on, so the user must be given a way to _explicitly_ signal an error (for example, if that sorted input array turns out not to be sorted). Providing strong static typing is merely a partial solution; no type system/language can describe all function domains (sorted arrays, for example). An error-signalling mechanism must be there anyway. In a dynamically-typed language, the user has no static typing `crutch' to lean on: if he wants to signal errors on bad arguments, he can code his own test to discover these arguments; if not (perhaps on quick-and-dirty throwaway code), he can omit them. Presumably user-defined functions intended for reuse and use by others would signal an error on bad arguments. Type declarations cover certain cases, making it easy for programmers in statically-typed languages to believe more precise argument validity checks (is that array sorted?) aren't needed. [To be honest, I wouldn't really check an array for sortedness in most cases, but just specify what happens if it's not, but hopefully you get the idea.] > > [Thus] I want a type system which > > (a) allows me to omit many type declarations (where unnecessary) > > (b) allows me to be very specific in constraining some arguments > >Yes, i'am too. > > >With dynamic typing, I can simply write predicates to constrain > >arguments/variables (Common Lisp, FL, some implementations of Scheme > >do this). > >... but i regard it as an abuse of notion to call such dynamic >calculated predicates types. You can model them just using conditionals >and an error halt function. What Common Lisp gives you here is simply >syntactic sugar in an essential untyped environment, and some >(rather restricted) kind of partial evaluation of this >predicates. Actually, what Common Lisp gives you isn't adequate at all. It allows primitives to behave in an undefined way on arguments out of their specified domain. Perhaps I'm biased, but having been exposed to CLU and FL, which both have completely specified primitive functions (with specific errors signalled on bad arguments or other error conditions), I really don't like a language with incompletely determined behavior. Your program may work one minute, but not the next, or on a different machine, and there will be no way to tell why... > >You static typing advocates claim that a static type system can do > >this for me, but I claim that it can't. A type language powerful > >enough to constrain arguments anywhere near as precisely as I need > >won't allow me to omit many types. > >My experience is the other way around. It would allow me to omit >many types, and in some rare cases, if have to add declarations. Perhaps you don't demand as powerful a type language as I do... (arbitrary type union, intersection, dependent types, higher-order functions) >However, unless I'am going to hack a one-night program, i would >always declare signatures at least for the exported functions >of a module even if they could be inferred. This seems to me >a reasonable kind of documentation. So would I. But, as I keep saying, any type language which allows internal declarations to be inferred won't allow me to specify the interfaces with the precision I need... > >Type inference is only possible > >for a limited class of type languages, and they tend to be fairly > >weak. In addition, certain programs are forbidden simply because they > >utilize types which can't be described by the type language used. > >This is theoretically right. But the question is, how often >instances outside this limited, weak class are required in >practice? For example, tell me an instance of practical evidence >for the need of self application! I can't come up with a quick example of self-application. However, I have written Lisp programs for which no type inference system could ever figure out what arguments certain functions would be applied to---for example, a lookup table of functions, each constrained in a complex way by program control to only be applied to correct arguments. Sure, I could build a big union type describing what sorts of functions could be in the table, and explicitly extract one of the appropriate type, but that just increases the number of things I have to keep up-to-date when I modify the program and add a new function to the table. I'd rather not have to cope with that when first trying something out. >Whats irritating me more and more in this discussion (as long as >I'am following it; the last week) is that the advocates of dynamic >typing claim for more expressivness, which *looks* like >the arguments of practical men, but in fact seems to be >more of philosophical nature. (This would be quite alright, but say >it like it is.) Actually, I think the practical argument is this: In certain situations (such as in protyping), I want to program in a completely unconstrained way. If one way of expressing things makes things easier, I should be able do it. ANYTHING which forces me to think about my program in an unnatural way will distract me from the problem at hand. A static type system forces upon me someone else's idea of how things should be done. A dynamic type system leaves me more free to do what ever moves me. Later I can worry about going back and making things safe for other people to use, or more efficient, or whatever. >Wolfgang Grieskamp >wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET -Brian Murphy brm@cs.stanford.edu
diamond@jit345.swstokyo.dec.com (Norman Diamond) (04/24/91)
In article <2046@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >However, static typing implies more >than that: it implies that expressions are constrained statically such >a way that all expressions can be assigned types from a restricted >set. The restricted set is such that machine representations can be >decided on at compile time Yes. >and such that no type information has to be >associated with a value at runtime. No. >My claim is not now, and never has been, that all static type checking >is bad, evil, or even rude. I am only saying that in situations such >as the above, it is the language implementator who should be taking >care of type tags rather than the programmer. Why not both? A lot of people might agree that dynamic typing is necessary in some cases too. Maybe someday the two sides might agree that the programmer should be allowed to specify types and/or classes when it helps catch errors and/or improve efficiency, etc.; and that the programmer should be allowed to omit specifications when it helps speed development time or extend reusability. >When the implementator >does it, it leads to less code, less complexity, and fewer bugs. No. When the programmer is allowed to do the portions that need it, it can also lead to less complexity and fewer bugs. -- Norman Diamond diamond@tkov50.enet.dec.com If this were the company's opinion, I wouldn't be allowed to post it.
cs450a03@uc780.umd.edu (04/24/91)
Norman Diamond: David Gudeman: >>... static typing implies ... all expressions can be assigned types >>from a restricted set ... such that machine representations can be >>decided on at compile time >Yes. >> and such that no type information has to >>be associated with a value at runtime. >No. No, "type information has to be associated with a value at runtime" ? Then what is dynamic typing? Or, was this a statement that "constant type tags can be assiciated with a value at runtime"? If so, it is far from a clear statement. >Why not both [dynamic and static typing] ? "Both" is, by definition, not static typing -- such a language would allow you to express statements which can not be statically typed. On the other hand, it is often a simple matter (of strength reduction) to extract static typing information from statements in a dynamically typed language. One distinction is that in a dynamically typed language type assertions are "just another operator" and most likely will use the same syntactic conventions as other language operators. In statically typed language this does not have to be the case, and usually is not. This was hashed out in comp.lang.misc about a month ago, though it might not have been expressed fully in any one article. Raul Rockwell
ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (04/24/91)
In article <1991Apr15.183805.10544@maths.nott.ac.uk>, anw@maths.nott.ac.uk (Dr A. N. Walker) writes [about Algol 60]: > In any case, dynamic > *own* arrays are a pig to compile; again, they were [?universally] > omitted from implementations. If I remember correctly, Burroughs Algol supported them. There never was any particular problem in compiling them; the problem was in chosing from several interpretations of what they should mean (not unlike C++, just like a head cold is not altogether unlike the Plague). The interpretation which was adopted for Algol 60.1 is that the bounds are determined the first time that the declaration is elaborated, and don't change after that. So for n := 1, n + 2 while n < 13 do begin own integer array a[1:n]; ... end; would have the array declaration compiled to something like static int already_done = 0; static struct Int_1D a; if (!already_done) { a.lb1 = 1; a.ub1 = n; a.data = malloc((a.ub1-a.lb1+1) * sizeof *(a.data)); already_done = 1; } This is a pig to compile? Of course, if the loop is for n := 11, n - 2 while n > 0 do begin own integer array a[1:n]; ... end; then you may be in trouble ... -- Bad things happen periodically, and they're going to happen to somebody. Why not you? -- John Allen Paulos.
gudeman@cs.arizona.edu (David Gudeman) (04/25/91)
In article <1991Apr24.051522.28988@tkou02.enet.dec.com> Norman Diamond writes:
]
]Why not both? A lot of people might agree that dynamic typing is
]necessary in some cases too. Maybe someday the two sides might agree
]that the programmer should be allowed to specify types and/or classes
]when it helps catch errors and/or improve efficiency, etc.; and that
]the programmer should be allowed to omit specifications when it helps
]speed development time or extend reusability.
That was the suggestion that spawned all of these threads. Some
people seemed to feel that any dynamic typing is too "dangerous" and
discussions of that led to all the rest.
]>When the implementator
]>does it, it leads to less code, less complexity, and fewer bugs.
[ ^ (meaing dynamic typing)]
]No. When the programmer is allowed to do the portions that need it,
]it can also lead to less complexity and fewer bugs.
That doesn't make any sense. How can it lead to less complexity and
fewer bugs when the implementer has to explicitely handle details
instead of letting them be handled by the language? Does it lead to
less complexity and fewer bugs when implementers handle array bounds
checking without language help? What about checking for dereferencing
a null pointer? Whenever you leave such things up to the implementer
you are providing opportunities for careless bugs.
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
wall@pa.dec.com (David Wall) (04/25/91)
David Gudeman writes, of the claim that static typing leads to less complexity: That doesn't make any sense. How can it lead to less complexity and fewer bugs when the implementer has to explicitely handle details instead of letting them be handled by the language? Does it lead to less complexity and fewer bugs when implementers handle array bounds checking without language help? What about checking for dereferencing a null pointer? Whenever you leave such things up to the implementer you are providing opportunities for careless bugs. Since when does static typing preclude runtime checks? Ever hear of Pascal? Or Modula-2? The Modula-2 I use checks for range errors, following nil pointers, even following bad but non-nil pointers. Moreover, the optimizer is good enough to get rid of most of these checks, when they are provably redundant. David W. Wall - - - - - - - - - - - - - - - - < wall@decwrl.dec.com >
gudeman@cs.arizona.edu (David Gudeman) (04/25/91)
You left off the context necessary to see that I was not claiming anything so stupid as the above. The above reply is a complete non-sequiter. Please read more carefully before you follow-up, David. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
gudeman@cs.arizona.edu (David Gudeman) (04/25/91)
xxx
That's twice in the last week I seem to have been bitten by the
mythical line eater that I never believed in before. Let me try
again:
In article <1991Apr24.212350.27855@pa.dec.com> David Wall writes:
]
]Since when does static typing preclude runtime checks? Ever hear of
]Pascal? Or Modula-2?
You left off the context necessary to see that I was not claiming
anything so stupid as the above. The above reply is a complete
non-sequiter. Please read more carefully before you follow-up, David.
Let's see if that gets through...
--
David Gudeman
gudeman@cs.arizona.edu
noao!arizona!gudeman
markv@pixar.com (Mark VandeWettering) (04/25/91)
In article <1707@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <1991Apr9.110217.10963@mathrt0.math.chalmers.se> Lennart Augustsson writes: >]In article <1593@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >] map f [] = [] >] map f (x:xs) = f x : map f xs >I'm getting really tired of pointing this out: the program above does >not have the full generality of the mathematical or the dynamically >typed version. This is a point I've made several times on several >Haskell and ML programs, and I wish people would get the idea so I >could stop repeating myself. The statically typed program only works >on structures in which all elements have the same type -- and only >when the compiler can infer that type. Well, in a language with strong typing, the function f has a particular type (alpha -> beta). It doesn't make sense to have a function which applies to more than a single type. Note: I do understand the motivations for weak (you may call it dynamic if you like) typing. There are many distinct advantages to both side. >Type inference has some nice features, but it does _not_ give you the >expressive power of dynamic typing. Hard statement to prove, and almost meaningless. Does it mean you can't express the same programs? In the same or fewer lines of code? On what kind of an example.... Mark
wall@pa.dec.com (David Wall) (04/26/91)
David Gudeman admonishes: You left off the context necessary to see that I was not claiming anything so stupid as the above. The above reply is a complete non-sequiter. Please read more carefully before you follow-up, David. and on reviewing his article I see that he is right; he was not claiming that static typing precluded runtime checks, he was drawing an analogy between runtime typing and runtime checking. I apologize. I think it's a false analogy, which is why I misunderstood, but I do apologize. :-) David W. Wall - - - - - - - - - - - - - - - - < wall@decwrl.dec.com >
gudeman@cs.arizona.edu (David Gudeman) (04/26/91)
In article <1991Apr25.165342.14354@pixar.com> Mark VandeWettering writes: ]In article <1707@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]>...the program above does ]>not have the full generality of the mathematical or the dynamically ]>typed version. ]Well, in a language with strong typing, the function f has a particular ]type (alpha -> beta). It doesn't make sense to have a function which ]applies to more than a single type. It certainly does. I do it all the time. ]Note: I do understand the motivations for weak (you may call it dynamic if ]you like) typing. There are many distinct advantages to both side. There is a difference between weak typing and dynamic typing. Lack of understanding of this has resulted in far too much controversy, especially since the definitions have been posted. To repeat: weak typing means that the language may have undefined behavior like infinite loops or core dumps from a type error. Dynamic typing means that it is not generally possible to assign a machine representation to the values that will be returned by all syntactic expressions. You can still get strong typing in this case by including type information with the data at runtime. ]>Type inference has some nice features, but it does _not_ give you the ]>expressive power of dynamic typing. ]Hard statement to prove, and almost meaningless. Does it mean you can't ]express the same programs? In the same or fewer lines of code? On what ]kind of an example.... It means that most programs are smaller and simpler in dynamically typed languages than in statically typed languages. It is not all that hard to show this: just compare the size of all the .h files in C programs to the total size of the program. As a rough approximation, the .h files are the extra baggage caused by static typing. That is a very rough approximation though, since dynamic typing enhances reusability of code. So a lot of reduncancy in the .c files should be eliminated also. -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
diamond@jit533.swstokyo.dec.com (Norman Diamond) (04/26/91)
David Gudeman Norman Diamond Raul Rockwell dg>>>... static typing implies ... all expressions can be assigned types dg>>>from a restricted set ... such that machine representations can be dg>>>decided on at compile time nd>>Yes. dg>>> and such that no type information has to dg>>>be associated with a value at runtime. nd>>No. rr>No, "type information has to be associated with a value at runtime" ? Type information might or might not be needed at runtime, depending on the type. Actually did not distinguish between static typing and static classing, since no one else seems to be interested in distinguishing these. rr>Then what is dynamic typing? From what I've seen in this argument, dynamic typing is the absence of programmer specification of types. rr>Or, was this a statement that "constant type tags can be assiciated rr>with a value at runtime"? If so, it is far from a clear statement. Why would type tags have to be constant? nd>>Why not both [dynamic and static typing] ? rr>"Both" is, by definition, not static typing -- such a language would rr>allow you to express statements which can not be statically typed. Indeed, "both" means that the language would allow you to express statements which cannot be statically typed, and also statements which can be statically typed, and also statements which are constrained to static types by administrative fiat. rr>On the other hand, it is often a simple matter (of strength reduction) to rr>extract static typing information from statements in a dynamically rr>typed language. "Often" is the key word in that sentence. It is often possible to write C programs without the use of "void *", but someone decided that "often" isn't "often enough." >One distinction is that in a dynamically typed language type >assertions are "just another operator" and most likely will use the >same syntactic conventions as other language operators. Oh, this sounds like a "both" ... haven't seen one of those yet. >In statically >typed language this does not have to be the case, and usually is not. Once upon a time, "uniformity of reference" was a buzz-phrase. People liked Fortran and PL/I better than Algol because array element selection looked like function calls, and they would have liked structure selection to look like function calls too, so that the client would not have to "know" the kind (I don't want to say "type" this time) of structuring involved to retrieve the data. However, the Pascal and C style of square brackets for arrays and dot for structures (and parentheses for function calls) seems to have won. I like it too. It seems to reduce confusion. I think that to use different syntax to distinguish static properties of an entity from its dynamic properties also might reduce confusion. >This was hashed out in comp.lang.misc about a month ago, though it >might not have been expressed fully in any one article. Sorry, I wasn't reading comp.lang.misc then. -- Norman Diamond diamond@tkov50.enet.dec.com If this were the company's opinion, I wouldn't be allowed to post it.
diamond@jit533.swstokyo.dec.com (Norman Diamond) (04/26/91)
In article <2397@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: >In article <1991Apr24.051522.28988@tkou02.enet.dec.com> Norman Diamond writes: nd>> Maybe someday the two sides might agree nd>>that the programmer should be allowed to specify types and/or classes nd>>when it helps catch errors and/or improve efficiency, etc.; and that nd>>the programmer should be allowed to omit specifications when it helps nd>>speed development time or extend reusability. dg>>>When the implementator dg>>>does it, it leads to less code, less complexity, and fewer bugs. dg> ^ (meaing dynamic typing) ^ sorry, I thought you just meant "typing" nd>>No. When the programmer is allowed to do the portions that need it, nd>>it can also lead to less complexity and fewer bugs. dg>That doesn't make any sense. Agreed. I think the implementor should do dynamic typing. However, the programmer should specify the types of the static parts and specify which parts are dynamic. (Well never mind; this opinion got me fired from my previous job, and is neither understood nor relevant at my present one.) -- Norman Diamond diamond@tkov50.enet.dec.com If this were the company's opinion, I wouldn't be allowed to post it.
mathew@mantis.co.uk (mathew) (04/26/91)
wall@pa.dec.com (David Wall) writes: > David Gudeman writes, of the claim that static typing leads to less > complexity: > That doesn't make any sense. How can it lead to less complexity and > fewer bugs when the implementer has to explicitely handle details > instead of letting them be handled by the language? Does it lead to > less complexity and fewer bugs when implementers handle array bounds > checking without language help? What about checking for dereferencing > a null pointer? Whenever you leave such things up to the implementer > you are providing opportunities for careless bugs. > > Since when does static typing preclude runtime checks? He didn't claim that it did. Someone said "It's less complicated to let the programmer handle dynamic typing rather than the implementor". He said "Does it lead to less complication to let the programmer handle array bounds checking rather than the implementor?" The expected answer was "No", as you rightly point out; the question was asked in order to show up a flaw in the original statement. To reiterate: In general, it is less complicated to use implementor-provided features than to have to implement them yourself. It is simply not true that it is easier, less complicated or safer to write your own dynamic typing system than it is to use someone else's debugged system. > The Modula-2 I use checks for range errors, following nil pointers, > even following bad but non-nil pointers. Yes, but if we were to use the static typing enthusiasts' arguments, we should be writing a language which doesn't have pointers. That way you don't have to do time-consuming pointer-checking tests at run time, and you don't run the risk of having a pointer error occur during program execution. [ Replace "pointer" with "type" in the above paragraph and you have exactly their argument. ] > Moreover, the optimizer is good enough to get rid of most of these > checks, when they are provably redundant. Right. Which is what the dynamic type-checking people are suggesting: give the programmer a CHOICE. And if you can remove the dynamic typing at compile time then do so. mathew [ With subtitles for the hard of thinking ] -- If you're a John Foxx fan, please mail me!
anw@maths.nott.ac.uk (Dr A. N. Walker) (04/26/91)
In article <5388@goanna.cs.rmit.oz.au> ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes: >If I remember correctly, Burroughs Algol supported [dynamic own arrays]. >There never was any particular problem in compiling them; >the problem was in chosing from several interpretations of what >they should mean (not unlike C++, just like a head cold is not >altogether unlike the Plague). The interpretation which was >adopted for Algol 60.1 is that the bounds are determined the >first time that the declaration is elaborated, and don't change >after that. Well, yes, there's no difficulty compiling that interpretation. But it could be [and was] argued that the bounds should be re-determined every time, and that any common elements should be preserved. Eg, imagine an implementation of Conway's Life, in which, for efficiency, only the active part of the board is retained. We might have own Boolean array board [lorow:hirow, locol:hicol]; where "lorow" (etc) are integer procedures returning the first row (etc) that needs to be considered. As the "game" evolves, this declaration is re-elaborated every generation, and the active region might march all over the place and then back again. Perfectly reasonable from the user's point of view, not so nice for the compiler. Possible, but not nice. >This is a pig to compile? Quote from "Algol 60 Implementation", Randell & Russell, Academic Press, 1964 [a detailed description of the Walgol interpreter, which has very few restrictions], p271: "(vi) Dynamic Own Arrays are not Allowed [...] The difficulty of implementing dynamic own arrays is that the size and shape of an own array, whose elements must be retained when the block in which it is declared is left, may change on subsequent entries to the block. A proposed technique for implementing dynamic own arrays is described in a paper by Ingerman" [CACM 4, 1, pp 59-65, 1961]. So R&R clearly interpreted dynamic own arrays to include the case above. -- Andy Walker, Maths Dept., Nott'm Univ., UK. anw@maths.nott.ac.uk
tmb@ai.mit.edu (Thomas M. Breuel) (04/29/91)
To repeat: weak typing means that the language may have undefined behavior like infinite loops or core dumps from a type error. Dynamic typing means that it is not generally possible to assign a machine representation to the values that will be returned by all syntactic expressions. You can still get strong typing in this case by including type information with the data at runtime. Conversely, you can also get dynamic typing in statically typed languages by creating unions ("datatype" in SML). At least in a language like SML, the static type system is an additional, very useful facility offered by the compiler. Most people who program in statically typed languages write dynamically typed code in some places; they simply restrict it to particular sections of their programs. Unfortunately, the notation for expressing dynamic typing in many statically typed languages is a little cumbersome, requiring explicit tagging and untagging by the programmer. However, C++, because of user-definable conversion operators, lets you use dynamic typing a little more easily, and ECL (an old Lisp-like language) had an even smoother integration of dynamic and static typing. It means that most programs are smaller and simpler in dynamically typed languages than in statically typed languages. Dynamically typed programs are also impossible to prove type-correct, and many trivial errors go unchecked. It is not all that hard to show this: just compare the size of all the .h files in C programs to the total size of the program. As a rough approximation, the .h files are the extra baggage caused by static typing. That is a very rough approximation though, since dynamic typing enhances reusability of code. So a lot of reduncancy in the .c files should be eliminated also. As in most fields of human endeavour, redundancy is something to be aimed for, not something to be eliminated, since it helps catch mistakes. >>Type inference has some nice features, but it does _not_ give you the >>expressive power of dynamic typing. Dynamic typing is necessary for serious program development, since there are some things that are simply too cumbersome to express in a static type system. However, dynamic typing should not be the rule. For the proverbial "99%" of all programming tasks, static typing is more robust, and sufficiently expressive (at least, if it is polymorphic). As a bonus, statically typed languages tend to be easier to compile into efficient code with current compiler technology and machine hardware. Thomas.
Chris.Holt@newcastle.ac.uk (Chris Holt) (04/29/91)
mathew@mantis.co.uk (mathew) writes: >Yes, but if we were to use the static typing enthusiasts' arguments, we >should be writing a language which doesn't have pointers. Just to add to the diversity of opinion :-), some of us think that static typing is often very useful, that dynamic typing is sometimes the best thing to use, but that pointers should never be visible to the programmer. But I don't want to restart the pointer wars again... ----------------------------------------------------------------------------- Chris.Holt@newcastle.ac.uk Computing Lab, U of Newcastle upon Tyne, UK ----------------------------------------------------------------------------- "And when they die by thousands why, he laughs like anything." G Chesterton
mathew@mantis.co.uk (mathew) (04/29/91)
diamond@jit533.swstokyo.dec.com (Norman Diamond) writes: > I think the implementor should do dynamic typing. However, the > programmer should specify the types of the static parts and specify which > parts are dynamic. (Well never mind; this opinion got me fired from my > previous job ^^^^^^^^^^^^^^^^^^^^^^^^^ You *are* joking, aren't you? mathew -- If you're a John Foxx fan, please mail me!
wg@opal.cs.tu-berlin.de (Wolfgang Grieskamp) (04/30/91)
[Excuse me, but this is somehow out of time...] brm@neon.Stanford.EDU (Brian R. Murphy) writes: >I fail to see how parameterization is relevant. But yes, the case of >of a possible error value _is_ a more common case of my example. For >example, a Lisp expression such as > (if (= y 0) (error) (/ x y)) >would have type [something like] (error + int). So either we must >automatically include errors in every base type, introduce an explicit ^^^^^^^^ >union declaration in just about every function, or complicate things a >bit by going to a continuation-passing semantics. Other examples >besides errors _do_ come up, though, so why not just adopt a more >general solution? Yes, why not? The general solution (for this class of problems) may be "anonymous unions". For the example, its inferrable. Parameterization in combination with anonymous unions might be of use for instance to define error tolerant function composition: compose:: ('a -> ('b + error)) * ('b -> ('c + error)) -> 'a -> ('c + error) compose(f,g)(x) = if f(x) isa error then f(x) else g(f(x)) The signature of compose will be inferrable again. >Perhaps you don't demand as powerful a type language as I do... >(arbitrary type union, intersection, dependent types, higher-order >functions) Well, maybe I'am not so idealistic in respect to the level of the workaday problems in program developement. However, probably no one ever worked with higher-order functions will dispense with it. So it might become some day with dependent types, although its hard to imagine (for me at least, currently. printf, *the* example for dependent typing, is not the function I use all the day ...). I agree completely with the statement someone else posted recently: essential, playing with dynamic types opens the mind for recovering some more avangardistic programming techniques and inspires the resarch in typing theory. Theory about typing is theory about programming. I wonder if theory can be build up reasonable from dynamic typing :-? -- Wolfgang Grieskamp wg@opal.cs.tu-berlin.de tub!tubopal!wg wg%opal@DB0TUI11.BITNET
stephen@estragon.uchicago.edu (Stephen P Spackman) (05/02/91)
In article <2450@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: |There is a difference between weak typing and dynamic typing. Lack of |understanding of this has resulted in far too much controversy, |especially since the definitions have been posted. | |To repeat: weak typing means that the language may have undefined |behavior like infinite loops or core dumps from a type error. Dynamic |typing means that it is not generally possible to assign a machine |representation to the values that will be returned by all syntactic |expressions. You can still get strong typing in this case by |including type information with the data at runtime. This statement confuses the *hell* out of me. I thought I knew all about this issue; goodness knows I've been immersed in enough of it. But it seems to me that it is **DYNAMIC** typing that is generally implemented with every object an (underlying) dependant product - i.e. with every type having the SAME and thus PREDICATBLE machine representation; and it is **STATIC** typing which permits the representational consistency constraints to be optimised out so that the machine representations are arbitrary and NOT interpretable except by reference to the inductive type structure of the actual code! DYNAMIC typing uses tags always, STATIC typing uses tags on programmer request (i.e. in unoptimisable discriminant fields). DYNAMIC typing uses uniform representation, STATIC typing uses variant representation. It's just what you'd expect: the more work the compiler does, the more "compiled" the result. Look at the identity function. In a dynamically typed system (assuming strong typing throughout, here) it has "type" OBJECT -> OBJECT (i.e., it's a monadic function, that's the whole story). OBJECT has an underlying representation like this: [TYPE T; T t] (though maybe the tag T is moved up into the pointer in some cases), and this is true whether the value of T is Integer, Float, List, whatever. Conversely, in a statically typed system, I has type FORALL T:TYPE. T -> T, and the underlying representation of the argument and of the result can ONLY be determined by examination of the lexical call site (or, presumably, in the machine representation, by the examination of the particular instance of T passed in as an extra, hidden, parameter). In particular, while the value of the formal argument type T may be passed in as part of the calling convention, I don't see why it should ever be passed out again.... -very confused stephen ---------------------------------------------------------------------- stephen p spackman Center for Information and Language Studies systems analyst University of Chicago ----------------------------------------------------------------------
gudeman@cs.arizona.edu (David Gudeman) (05/03/91)
In article <STEPHEN.91May2030111@estragon.uchicago.edu> Stephen P Spackman writes: ]In article <2450@optima.cs.arizona.edu> gudeman@cs.arizona.edu (David Gudeman) writes: ]|... Dynamic ]|typing means that it is not generally possible to assign a machine ]|representation to the values that will be returned by all syntactic ]|expressions. You can still get strong typing in this case by ]|including type information with the data at runtime. ]This statement confuses the *hell* out of me. This response confuses the hell out of me. ]DYNAMIC typing uses tags always, What did you think I meant by "including the type information at runtime"? -- David Gudeman gudeman@cs.arizona.edu noao!arizona!gudeman
nix@asd.sgi.com (Sold to the highest Buddha) (05/03/91)
stephen@estragon.uchicago.edu (Stephen P Spackman) writes: > DYNAMIC typing uses tags always ... unless the typing can be completely determined at compile time, and the tags can be optimized out. It seems to me that a language with a good type inference algorithm and optional type constraints provided by the programmer should be able to do this in many cases, eliminating type tags when they aren't necessary. If you're a static typing fan, you tell the compiler to whine at you if it can't optimize out all type tags at compile time, and you have to provide extra type information to get the code to compile. Is there any reason why this can't be done? Are there any "dynamically typed" languages that do this? Nix Thompson nix@sgi.com ...!uunet!sgi!nix follow the black valley - trail of death into the beautiful sunshine.
olson@juliet.ll.mit.edu ( Steve Olson) (05/03/91)
In article <NIX.91May2231654@valis.asd.sgi.com> nix@asd.sgi.com (Sold to the highest Buddha) writes:
It seems to me that a language with a good type inference
algorithm and optional type constraints provided by the programmer
should be able to do this in many cases, eliminating type tags when
they aren't necessary. If you're a static typing fan, you tell the
compiler to whine at you if it can't optimize out all type tags at
compile time, and you have to provide extra type information to get
the code to compile.
Is there any reason why this can't be done? Are there any
"dynamically typed" languages that do this?
Your preceeding wish list sounds like a design document for CMU's
"Python" Commom Lisp compiler. I can't vouch for it personally,
(am eagerly awaiting a SunOS version) but the user's manual and compiler
design notes (FTP-able from CMU) look promising.
Nix Thompson nix@sgi.com ...!uunet!sgi!nix
--
-- Steve Olson
-- MIT Lincoln Laboratory
-- olson@juliet.ll.mit.edu
--
rockwell@socrates.umd.edu (Raul Rockwell) (05/03/91)
Stephen P Spackman > > DYNAMIC typing uses tags always Nix Thompson > ... unless the typing can be completely determined at compile time, > and the tags can be optimized out. > Is there any reason why this can't be done? Are there any > "dynamically typed" languages that do this? The compiler I use does this almost all the time. However, it is mostly used for compiling "leaf" functions to be used in an interpreted environment. Basically, its only purpose is to cut out interpreter overhead (including type-checking) for problems that involve a lot of little operations on the same piece of data. The interpreter has better performance in other problem domains. (the sort of optimizations compilers do IS a tradeoff...). Raul Rockwell