On 16/05/2011, at 1:25 AM, Graydon Hoare wrote:
>
> - We really were wasting a whole lot of time chasing memory errors!
Those people using C, yes. They still do :)
> - Tooling matters a lot. The IDEs are incredible.
I'll take your word on that, I never use IDE's. In my view Java's really
big selling point was the standard GUI. Even if it wasn't that good,
at least it was standard.
> - Fast compilation is mostly a matter of compilation model.
Yes, but surely that's clear?
> - Well defined primitive types are handy.
> - There's a large class of business programmers who value
> predictability over many other features.
Yes, but it is worth noting: in Java, semantic predictability.
In C++, the semantic predictability is much better than C though
not quite as strong as in Java. However in C++ the performance
predictability of the library is entrenched in the ISO Standard.
> - The line between dynamic and static language is blurrier than
> everyone was saying.
We might well agree that all (imperative) programs are dynamic.
Actually the way I see this issue is: we want strong expressive type systems
to help get programs right. However if the type system is strong but not
expressive enough the program must go through contortions to work around
it in some cases, which is not good.
The boundary here seems to be: basic typing is fully understood, up to
first order polymorphism at least, however high order polymorphism,
kinding systems, and polyadic programming (functorial polymorphism)
are not well understood and the work around for this lack of knowledge
which means a expressivity in a type system is insufficient is to use
dynamics, which is why fully dynamic languages can often do things
easily that you can't do at all in static languages.
A simple example: in C++ you can't write a function that returns itself
because there's no way to specify the type. In Python you can just do it.
Another example: it is HARD to get the typing strong enough to type
arrays correctly. Many languages (including C++, Ocaml and Rust)
just give up and don't bother to include the length in the type.
Felix provides arrays which include the length in the type and actually
allows *some* algorithms to be written with these but other, clearly
sensible, useful, and correct algorithms, cannot be written.
For example in Felix you can't concatenate two arrays because the lengths
can't be added by the type system.
> I don't know if there's a single concern I hold to be "paramount" in rust,
> but if I had to pick one it'd be "safety", which really isn't at play here.
> Polymorphism is by no means a dominant concern for me. It's a nice thing to
> support where and when we can, in whatever senses seem reasonable, and when
> we can't everyone knows how to work around its absences.
I think we differ in our viewpoints here. I agree safety is vital, and I think
you believe
performance is vital too.
Where perhaps we differ is that I believe polymorphism is essential for safety.
Roughly speaking it is "safer" to write a list or tree handling algorithm once
that handles all data types than it is to write it once for each data type.
I think you would probably agree with this if I put the word "better" rather
than "safer", but I believe that reducing duplicate efforts reduces bugs
even if it isn't "safety" in the sense of language enforcing things.
[Actuallly with polymorphism the language does "enforce" correct
copying for different data types by doing the copying itself, this certainly
prevents the programmer making mistakes!]
>
> (I don't even think in general it's a well-defined term. There are several
> perfectly valid senses to the word, and some of them are at odds in
> implementation technique. How do multimethods, overloading, subtyping &c fit
> into your worldview?)
Is that question rhetorical?
if not:
Multimethods don't work, by which I mean they do not fix the problem that OO
fails
to deliver abstraction and therefore is not a general paradigm.
Overloading is a convenience. Felix has it, and it introduces very significant
extra
complexity in the compiler. It is an almost essential convenience for handling
things
like operations on a large set of integer approximations that C has, for example
(although you could also do this with some other technique, for example
Haskell style type classes (which Felix also has) or Ocaml style functors).
I do not really like overloading: I can live with a language that doesn't have
it,
although I find it is a bit easier sometimes to use one that does.
Subtyping in the sense of type theorists is interesting and probably essential
for more complex coding. Note I'm talking about subtyping defined by
"a subtype of a type is a type with a subset of the values", not the
kind of subtyping purported to exist in OO systems. I do not really understand
enough to appreciate how useful it is or isn't. I used Ocaml polymorphic
variants
which support subtyping variants particularly well and found it was still
extremely
hard to use in practice (but that may have been the particular implementation
or just my own inability).
>
>> Of course if you don't agree polymorphism is important, you're not accepting
>> the premise, so that's fine. And if my argument is incorrect I'd love to have
>> that demonstrated.
>
> Ok. Demonstration A: every language with less-than-Ideal (in whatever sense
> you mean) polymorphism but perfectly usable pragmatics, currently having
> millions of lines of code written in it every day. Code that ships and works
> and is not totally worthless.
Please excuse my language, I often exaggerate and get too emotional .. I don't
actually mean to
offend people.
Of course, for example "C" is a useful language. But that doesn't mean a *new*
language
should be developed that doesn't support polymorphism. Of course I see that Rust
does (or is intended to) support polymorphism so we are actually arguing about
a detail.
Roughly: with multi-argument functions you can certainly have "true and proper"
polymorphism,
but not first order or even second order: you need a kinding system as well,
because your
functions are a combination of a N-functor (constructor) and an ordinary
function.
Languages with tuples have a family of N-functors for constructing tuples, but
most do not have the power to generalise them. Its not perfect but the rest of
the typing generalises (can be easily made polymorphic).
The impact on Rust is more severe because ALL functions will need the advanced
kinding support that most language lack only for tuples.
Just to be clear what I mean: in ML and in Felix you simply cannot write a
single
function that creates a parallel composition of N functions. Here is a binary
parallel composition in ML:
let pcomp2 f g = fun x y -> f x, g y
It is of course trivial to do this for a list of any length with recursion (in
fact a map),
but to do this for tuples of any number of components is impossible in ML.
It is however quite clearly trivial to implement pcomp for all N inside a
compiler,
but providing the support for the end user to put this in a library function is
very hard.
> See language list below...
>
>> Please understand: I wrote the article in attempt to prevent yet another
>> language
>> making a serious mistake, and thereby forfeiting all its good features.
>> Rust has many good features and some interesting ones (like the 3 level
>> memory management system).
>
> I'm afraid I don't see how some loss of compositionality (or polymorphism)
> equates to forfeiting all good features.
Please note "composition" was just an example of a polymorphic function.
> How do you get from "lack of compositionality" to "forfeit all other
> features"?
Lack of polymorphism, not composition.
The way I get from that is that theory and practice for non-polymorphic
sequential languages is well enough
known it is of little interest and there are no serious variations that really
affect software
quality. All the research is about type systems and supporting polymorphism
better.
[Note: "sequential languages" excludes concurrency which is clearly not well
understood]
Polymorphism is the way to get type safety for components that are reusable for
many types.
To be more precise: everyone can easily do first order polymorphism. Second
order is a
bit harder. The holy grail is polyadic programming, also called functorial
polymorphism.
I want to give an example of what that is. Consider "fold", it is an important
higher order
function I'm sure you'd agree.
Now, in most languages fold is polymorphic. You have List.fold in Ocaml and it
works
for list of integers and lists of floats and even lists of lists of doubles.
Then you have Array.fold. It does the same job for arrays.
Then there is a Set.fold which does it for sets.
Etc etc..
So many folds! Its boring to write out all these folds! There must be a better
way.
To write fold ONCE and have it work on ALL data structures.
A language that can do that is polymorphic over data functors (data structures),
not just the type of values contained in them.
This is the holy grail. I have worked on a language which was array polyadic,
meaning all algorithms could be written to work independently of the number
of dimensions (and be statically type checked to ensure no mismatches).
Note: *number* of dimensions. Note just any length, but any number of
dimensions.
Subsequently, this was extended to ANY polynomial data type: thats a data type
made of of sums and products (variants and tuples of something similar).
That's a very wide class of data structures: lists, trees, etc: anything
inductively
defined (i.e. constructive data type).
Ok, so sorry for the long rave and digression but there's a purpose in it.
Since I see the aim as providing this kind of very high level of reusability ..
a language without basic polymorphism is back in the dark ages.
A language missing out on higher order polymorphism or polyadic programming
isn't at the research frontier but at least it implements something reasonable
well
established as a precursor to the "real thing". Felix is such a language: it
really
only has first order polymorphism (and a little tiny bit of higher order stuff,
just enough get Monads working .. but don't rely on it).
>
>> As I tried to explain I think you can keep aliases, and it will have the
>> desired effect *most* of the time even with tuples. If you're willing to
>> throw
>> out a few cases, you can go with tuples.
>
> Really? It sounded like something quite different to me: that the pragmatics
> of making a function call would change to entail implicit heapification on
> every library boundary. That would further entail:
>
> - Making calls to precompiled libraries using pointers less
> efficient than just passing by value (!), unless we..
>
> - Overhauled the whole compilation model to stick a copy of the
> text (say, pickled AST) in each library and rebuilt it every
> time we built final binaries. Compile times shoot up and we've
> got the C++ "recompile libstdc++ each time" system.
Note my suggestion on this: you should consider doing this
by only *optionally* recompiling.
The idea is that you use the precompiled functions when prototyping
and when you're happy you rebuild the lot as a single piece of text.
Yes you're "recompiling libstdc++" in that case but you're not doing
it during development, only when you make a release.
> - Also we could never do varargs.
Varargs are evil :)
>
> - Also we could never do keyword args.
Felix does, and it uses the tuple model!
And since it also has overloading it even allows the keywords
to help with overload selection.
>
> - Also mutable values would become significantly more mysterious
> because the compiler sometimes inserts copy-to-heap operations
> that change where the mutable thing is located.
>
> - Also the semantics of destructors and/or gc would change because
> we'd have phantom copies of things being synthesized by the compiler
> that we'd have to decide when and how to dispose of.
These things may happen, but this kind of thing is always going to happen
in any programming system. What I mean is: I might implement a graph data
type in C and write cleanup routine which is of course precisely a standard
garbage
collection algorithm (because there isn't any other way to manage memory
in a graph).
So even though you don't have GC in C, if you have a graph data structure
you have GC in C: its just in the user code instead of the language system.
>
> IOW it would change a lot of quite major pragmatics of the language.
I am not so sure, but of course you know Rust better. Removing alias clearly
doesn't "impact" the language at all. It does impact programmers trying to use
the language (since the feature is missing).
Once aliases are removed, changing from multiple arguments to tuples
has no impact (given you don't get have polymorphism or typing to support
it), but it opens the path for easy introduction of polymorphism. Introducing
that
also has no impact on the language (in the sense of breaking any invariants).
But again it affects programmers by allowing much safer implementations
data structures by making them more reusable.
I do NOT dispute the logic of your analysis, by the way. At this time I believe
you
are right: something you want to achieve (iron clad safety and zero overhead
memory management when using down stack pointers) is definitely lost.
There may be another way to achieve this though, and it may not matter quite so
much if you lose a bit of performance in some cases (we would agree losing
safety in Rust isn't acceptable: in Felix I would throw out the safety to get
the performance but I'm not arguing you should do that).
> I recognize that some compositionality of function types is being lost here,
> but I simply don't think the mathematical abstraction of a function with a
> single, purely compositional domain-and-range is an adequate model of what
> real programmers want to be doing (and are accustomed to doing elsewhere).
I'm sure it isn't but without meaning to be rude real programmers have no idea
what they're
talking about most of the time so that isn't much of an argument. Real
programmers think
OO is wonderful, that its the holy grail, when it can be proven to be
fraudulent in a few
lines.. but real programmers would then try to invent the Perpetual Motion
Machine
despite the fact physics says it can't be done (Multimethods, double dispatch
.. all sorts
of ridiculous attempts to support unsustainable religious beliefs).
Real programmers think about "what they want" in terms of "what they already
have".
Ask a C programmer what they wan't and they'll ask for some minor tweak of C.
They won't ask for ML because they have no idea what a variant type is, they
"have got by without higher order functions just fine" (etc).
Of course they keep producing code that doesn't work.
>
> I do think it's a model of a subset of what programmers want to be doing, and
> we support that model adequately. We have tuple types and inferred parametric
> polymorphism of a sort, and you can write your functions as pure and
> composable in FP style. We'll certainly ship FP bits to support this style in
> the standard library, as C++ increasingly does. I don't think C++ failed here
> at all; I don't think it's essential to convert *everything* to FP style to
> reap some of the benefits of it in some cases, and in cases where it's not a
> benefit it can be set aside in favour of a more appropriate style.
I should add in passing I am NOT an FP zealot. Felix is actually a classical
Algol like language,
and Ocaml is a fairly standard imperative language too, it just has strong
support for FP.
>
> I'm a pluralist and intend the language to support writing in multiple styles.
I am with you there.
> That includes FP, but it doesn't mean that the considerations of FP get to
> throw out the considerations of other styles to make sure everything composes
> perfectly in FP style.
There's no need, since we already HAVE the perfect FP language: Haskell.
>
>> I may be wrong. Maybe it isn't just a few cases. But I think it is worth
>> investigating
>> because you're paying huge price. unfortunately it isn't the kind of balance
>> than can be quantified.
>
> I agree it's very hard to quantify these things. It's a trade between
> compositionality and several other factors (predictability, performance, FFI,
> compiler complexity, compilation model, various non-tuple-like features) few
> of which are obvious or even visible in the FP worldview. I realize there's
> some compositionality being lost here, but like orthogonality, do not think
> it the sole concern.
I actually agree with all your arguments and much of your priorities, but not
your
view of polymorphism. I think you're playing down THE most important feature of
any programming
language because static typing is THE most important way to improve correctness
and
polymorphism is way of upgrading the expressivity of a type system by a huge
order of magnitude which leads to very significant reduction in the amount of
code that has to be written to implement libraries.
You are using static typing (and typestate stuff etc) to make memory management
guarantees beyond what most languages make, and these in turn provide
performance guarantees beyond which most languages make, and that is very
significant contribution to Computer Science (IMHO of course but I'm guessing
you aren't going to disagree on this one! :)
I just find it hard to believe that doing this requires sacrificing the usual
way of doing
polymorphism. "Aliases" are a hack. We both know that. You're using them
because you
can't see a better way to achieve your goals. I feel there just HAS to be a
better way.
i don't claim to know what it is. Its just intuition. Whatever it is should be
part of the type
system "in general" an not be restricted to function arguments. These down
stack
pointers are basically a special kind of weak pointer.
Anyhow thanks for your patience and time discussing this!
--
john skaller
[email protected]
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev