August 24, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Philippe Mori | In article <bi9df5$1bh7$1@digitaldaemon.com>, Philippe Mori wrote: > >> > So your definition of immuable that an object won't changes... but for a function parameter, does it apply to both the source and target or only to the target (as const in C++). >> >> What do you mean by source and target in this context? >> Immutable's just an object that will never change after it has been >> initialized. >> > > Suppose something similar to this: > > void f(int &target) { ... } > int source = 1; > f(source); > > The source is from where the data come... Can source be modified externally (other thread, alias, while f is executing. If it's immutable, then it cannot. If it's mutable, like in your example, it can. This also means that there is an fundamental incompatibility between mutable and immutable objects: one cannot be converted to the other, at least not without violating the basic rules of the language. (References changed to inout to look more like D. After all, this is a D newsgroup...) void f(inout immutable int); int source = 1; f(source); // compile-time error: can't convert int to immutable int If you could do the conversion, you might be able to change the object in another thread or via aliasing, and it would change without f() knowing. Which would make immutable the same as C++'s const, and prevent the precious compiler optimizations that we're trying to achieve here (or, are we? I actually doubt that they'd be so significant) >> > prohibited would be a function modifier that will ensure that the function is never called. >> >> Then why put the function there in the first place? :) >> > > I see a few cases: > > - The functions is implictly generated I guess there aren't implicitly generated functions in D currently (I might be wrong?) > - There are some implicit conversion that are allowed but does not give the desired result (for ex. in C++ std::string class can be initialized from 0.0 or false because of implicit conversion that occurs but are not desirable in that specific context). In practice, if we find such a bug, then we have an easy way to ensure we won't have it another time. I think D doesn't have as many opportunities to shoot oneself in the foot as C++, particularly with respect to implicit conversions... Then again, maybe there is someone to know better. > - One existing function become obsolete and we want to ensure that it is not used anymore but because there are other similar overload we like the compiler to trap them and issue an error. A keyword allows to make it clearer... Otherwise it is possible to do it with templates but it is more complicated... D has deprecated: http://www.digitalmars.com/d/attribute.html > - In generic (template) code, we might know some undesired types for arguments (like swapping one constant object or swapping objects of distincts type). If we have a prohibited keyword, it is a lot easier to prohibit what we don't want that to make code that would causes a compilation error in those cases... You might be wanting to abuse the deprecated (or private) qualifiers to do that kind of prohibiting. All of those keywords would play partly the same role, which isn't exactly orthogonal language design. I'd rather go in the direction of general compile-time error message which can have a lot more uses than just prohibiting instantiation of a certain function. class X { } template (Foo) { // your general template code goes here } template (Foo : X) { compileTimeError("You shouldn't instantiate Foo with types derived " "from X"); } Upon finding an "instance Foo(X)" the compiler would blurt out something like line 567: Cannot instantiate Foo(X) line 123: compileTimeError: You shouldn't instantiate Foo with types derived from X > I have done it (preventing code from compiling) a lot of times in > C++ when I was refactoring the code that have known problems. > I was using the compiler to help me find those problematic places... If you can find an example that isn't solved by other features in D, people are likely to be interested in seeing it. -Antti |
August 24, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antti Sykäri | Hi, Comments embedded. ----- Original Message ----- From: "Antti Sykäri" <jsykari@gamma.hut.fi> Newsgroups: D Sent: Tuesday, August 19, 2003 8:14 PM Subject: const / immutable (was: C++ style const concept) [big snip] > Now that I think of it, I remember that Daniel Yokomiso wrote an interesting description of const stuff some time ago, let's see... There, I see it's still worth reviewing: http://www.digitalmars.com/drn-bin/wwwnews?D/9962 Unfortunately my memory is not well lately and the newsgroup web interface seems to be down, so I talk something about this mutability issues. It's related to my pet language Eon. > (Daniel: how's Eon doing, by the way?) Eon is dead, long live Eon. Or something like that. The worst thing about designing a language is the pesky design decisions you have to make. I, after one year of study, dropped side-effects, and now Eon is a pure OOFPL. Here is the story of my attempts to provide immutability in the type-system. First some definitions: 1 - by side-effects I mean both observable and unobservable side-effects. 2 - I'll use procedures to describe methods with side-effects and functions to describe "pure", side-effect free, methods. 3 - mutable types are subtypes of immutable types, so we can call functions and procedures on mutable types and only functions on immutable types. 4 - a variable with an immutable type A holds a reference to some value of A, but this value can be mutated through other (mutable) references. My desire was to provide a way to verify, via static type checking, code correctness, specially regarding DbC. One of the most powerful contracts is the immutability contract, guaranteeing that some parameters of the operation will be untouched. int sum(immutable int[] array) { int result = 0; for (int i = 0; i < array.length; i++) { result += array[i]; } return result; } The "immutable" modifier indicates that the parameter won't be changed by the operation body. Notice that a immutable array of type T is distinct from a array of type immutable T; this "immutable" modifier refers to the array immutability, not the array element type immutability. This type modifier should be carried through assignment to avoid cheating, or else the operation could store the value in a mutable variable and break the immutability. There's a second way to use the "immutable" modifier: to define pure functions. int immutable product(immutable int[] array) { int result = 0; for (int i = 0; i < array.length; i++) { result *= array[i]; } return result; } An "immutable" operation won't cause side effects, so inside a class you can separate functions from procedures to ensure immutability. The compiler could enforce this rule disallowing procedure calls through immutable references. As soon as we define such rules we get in some problems: template TMin(T) { T immutable min(immutable T x, immutable T y) { return x < y ? x : y } } The min operation correctly asserts that it's parameters aren't mutated, but it incorrectly transform the immutable reference into a mutable one, using the "return". Here comes the first problem of immutability definitions: some operations return types depend on its parameter types. In these situations the return type mutability status is defined by the caller. Immutability depends heavily on the compiler, both to check the calls and to infer correctly the return type dependencies. There's also a third type of immutability: "immutable" types. But they're a beast of their own. In my language I decided to drop side-effects because: it's very expensive to track immutability and to provide two versions of the same operations (e.g. a put method that changes the collection and a put method that returns a new colection with the new value added). Also it'd very hard to write "ensure" clauses on method's contracts that have side-effects (google for "old" expression and clone in comp.lang.eiffel). Geting rid of side-effects allow the language and the libraries to become simpler, and we can simulate side-effects with linear types and monads. So, answering your question Antti, Eon is being rewritten by the last time. Today it's a pure OOFPL, with a higher-order features, a simple syntax to define object literals (i.e. "pair := {first := 1; second := "abcde"}"), predicate based multiple-inheritance (i.e. "class a := {x : Integer; y : Real and (> x) and odd};", we define x as an Integer and y as a Real number, greater than x and odd (odd and (> x) are predicates)). Unfortunately I had to restart the compiler (again) due to the syntatic and semantic changes. I believe this will be the last big change in the language and sometime in the next year (first semester probably) I'll have a working compiler and a standard library (collections, text, numerics, xml, io and networking) - most parts of the library are already specified (I have the method types and contracts and some implementations written in older versions of Eon). > -Antti Best regards, Daniel Yokomiso. "Nature has given us two ears but only one mouth." - Benjamin Disraeli (1804-81), British politician --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.512 / Virus Database: 309 - Release Date: 19/8/2003 |
August 25, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antti Sykäri | > > If it's immutable, then it cannot. If it's mutable, like in your example, it can. This also means that there is an fundamental incompatibility between mutable and immutable objects: one cannot be converted to the other, at least not without violating the basic rules of the language. > > (References changed to inout to look more like D. After all, this is a D > newsgroup...) > > void f(inout immutable int); > int source = 1; > f(source); // compile-time error: can't convert int to immutable int > If I want to uses an in parameter, does I have the choice between by value which I think is the default and by reference... So it would be more like void f(in byref immutable int) // More D style syntax or void f (in immutable & int the_param) // More like C++ To tell the compiler that I do not want to modify the_param and that it can assumes that it won't be modified externally. > If you could do the conversion, you might be able to change the object in another thread or via aliasing, and it would change without f() knowing. Which would make immutable the same as C++'s const, and prevent the precious compiler optimizations that we're trying to achieve here (or, are we? I actually doubt that they'd be so significant) > > > I guess there aren't implicitly generated functions in D currently (I > might be wrong?) > > > - There are some implicit conversion that are allowed but does not give the desired result (for ex. in C++ std::string class can be initialized from 0.0 or false because of implicit conversion that occurs but are not desirable in that specific context). In practice, if we find such a bug, then we have an easy way to ensure we won't have it another time. > > I think D doesn't have as many opportunities to shoot oneself in the foot as C++, particularly with respect to implicit conversions... Then again, maybe there is someone to know better. > > > - One existing function become obsolete and we want to ensure that it is not used anymore but because there are other similar overload we like the compiler to trap them and issue an error. A keyword allows to make it clearer... Otherwise it is possible to do it with templates but it is more complicated... > > D has deprecated: http://www.digitalmars.com/d/attribute.html > But does it is possible to control in which module or on some other condition when deprecated take effects. Maybe I am using 2 libraries that has some deprecated functions but I still want to uses them in the first one (the code depends a lot on them) but not on the second one... Also if the attribute can be depedant on other things (probably on versioning), then it might be possible to uses version for that purpose... > > - In generic (template) code, we might know some undesired types for arguments (like swapping one constant object or swapping objects of distincts type). If we have a prohibited keyword, it is a lot easier to prohibit what we don't want that to make code that would causes a compilation error in those cases... > > You might be wanting to abuse the deprecated (or private) qualifiers to > do that kind of prohibiting. All of those keywords would play partly the > same role, which isn't exactly orthogonal language design. I'd rather go > in the direction of general compile-time error message which can have > a lot more uses than just prohibiting instantiation of a certain > function. > > class X { } > > template (Foo) { > // your general template code goes here > } > template (Foo : X) { > compileTimeError("You shouldn't instantiate Foo with types derived " > "from X"); > } > > Upon finding an "instance Foo(X)" the compiler would blurt out something like > > line 567: Cannot instantiate Foo(X) > line 123: compileTimeError: You shouldn't instantiate Foo with types > derived from X > Compile time error is a must... This allows to check many constraints that are not directly supported by the language (like your example). This can be used to validate some constants to ensure no accidental changes would be done. This include some magic numbers, the size of a structure (it's alignment, member offset,...) and we should be able also to check some properties of classes and struct like: - Is it a POD type, auto type, class type, enum type,... - Is it modifiable - Does it have a given member or support a given operation (member function, copy constructor, default constructor, operator +, comparison, equality,...) - Is if final, abstract,... - Is it an interface (or a COM compatible interface) > > I have done it (preventing code from compiling) a lot of times in > > C++ when I was refactoring the code that have known problems. > > I was using the compiler to help me find those problematic places... > > If you can find an example that isn't solved by other features in D, people are likely to be interested in seeing it. > I haven't yet tried the D compiler but I think that it might be interesting to check what can be done in D form the following source (and others): - Modern C++ Design (book) & Loki - C++ Templates (book) - STL - boost - ATL - C#, .NET and managed C++ - Xena ... We would then be able to compare the power and the simplicity of the language for some concrete things... and if something cannot be done directly, how hard it is to be done in D (if he can be done) or is it necessary (or we have better alternatives). It would also be interesting to check from some of well known book like Effective C++, Effective STL, and others that give some rules how to uses C++ more safely, how D compare (avoid problem at least implicitly while retaining enough power to be usefull...) |
August 25, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Philippe Mori | In article <bid8ts$12qg$1@digitaldaemon.com>, Philippe Mori wrote: >> (References changed to inout to look more like D. After all, this is a D >> newsgroup...) >> >> void f(inout immutable int); >> int source = 1; >> f(source); // compile-time error: can't convert int to immutable int >> > > If I want to uses an in parameter, does I have the choice between by value which I think is the default and by reference... > > So it would be more like > > void f(in byref immutable int) // More D style syntax Whoops :) Of course immutable objects would be "in" since they cannot be changed. So I'd say: void f(in immutable int); or void f(in immutable Big_Object); Were this legal D, the compiler would be free to pick either by value or by reference (whichever it would think to be more efficient) -- the semantics wouldn't change. If it were const, the semantics _would_ change; consider int f(inout const int x, inout y) { y++; return x; } void main() { int a = 1; printf("%d\n", f(a, a)); } If f gets y and x by reference, main() will print 2, and if by value, it will print 1. This is one of the places where const can deceive. D doesn't specify by-reference or by-value, instead it relies on in/out/inout, as mentioned in the "Functions" section. >> D has deprecated: http://www.digitalmars.com/d/attribute.html > > But does it is possible to control in which module or on some other condition when deprecated take effects. Maybe I am using 2 libraries that has some deprecated functions but I still want to uses them in the first one (the code depends a lot on them) but not on the second one... Sounds like a quality of implementation issue; DMD has option: -d allow deprecated features I'd say that in this case, the answer is "yes" if you build the two projects separately. -Antti |
August 26, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | In article <biafl2$2tsa$1@digitaldaemon.com>, Daniel Yokomiso wrote: > Eon is dead, long live Eon. Or something like that. The worst thing > about designing a language is the pesky design decisions you have to make. > I, after one year of study, dropped side-effects, and now Eon is a pure > OOFPL. Here is the story of my attempts to provide immutability in the > type-system. First some definitions: > > 1 - by side-effects I mean both observable and unobservable side-effects. > 2 - I'll use procedures to describe methods with side-effects and functions > to describe "pure", side-effect free, methods. > 3 - mutable types are subtypes of immutable types, so we can call functions > and procedures on mutable types and only functions on immutable types. > 4 - a variable with an immutable type A holds a reference to some value of > A, but this value can be mutated through other (mutable) references. So this would be equivalent to C++'s const. (Without the possibility of subverting the type system, though.) I'll blabber a bit more about immutability below. > int sum(immutable int[] array) { > int result = 0; > for (int i = 0; i < array.length; i++) { > result += array[i]; > } > return result; > } > > > The "immutable" modifier indicates that the parameter won't be changed > by the operation body. Notice that a immutable array of type T is distinct > from a array of type immutable T; this "immutable" modifier refers to the > array immutability, not the array element type immutability. This type One interesting question regarding the semantics of immutable here is that what is the difference between immutable array of int and array of immutable int. Because the array can be viewed as consisting of its elements, doesn't its immutability imply the immutability of its elements and therefore make it equivalent to immutable array of immutable integers? It seems to depend on the kind of array and of the kind of the language it lives in. I'd assume (in pseudo-code notation) that array in a very "pure" modern, orthogonal, clean, type-safe etc. language (I'm putting some expectations on Eon you see after all those papers you've read :-) would have operations like: class immutable_array<T> { T get(int); // return by-value copy of element at i int length(); } class array<T> extends immutable_array<T> { void set(T, int) // set value at T to int void length(int); } This would mean in practice that having an immutable array would prohibit changing its elements, which would render them practically immutable. On the other hand, if the hypothetical language were less clean and had something as down-and-dirty as aliasing and references, what would an array then look like? Like this?: array<T> { T& get(int); // return a reference to element at i // other properties } Now what would the "immutable part of the interface" look like? Would it have the function "immutable T& get(int)"? But this would have the same signature, except for the hidden argument that contains the reference to the object the method is part of. Ah, there's the catch: immutable_array<T> { immutable T& immutable get(int); // other properties } And lo, we get C++ rules along with all the special cases and all. (There probably is some kind of logic to them after all, especially if you remove the warts induced by the C compatibility.) If you have by-reference object, say of type T, what does "immutable T" mean? That you cannot change the reference to point to another object? Or that you can call only the pure methods of the object? Are the rules different if you have value objects? > modifier should be carried through assignment to avoid cheating, or else the operation could store the value in a mutable variable and break the immutability. There's a second way to use the "immutable" modifier: to define pure functions. At this phase I must point out that the semantics seem awfully familiar from C++, only the names differ: method - member function immutable - const pure function - const member function > An "immutable" operation won't cause side effects, so inside a class you > can separate functions from procedures to ensure immutability. The compiler > could enforce this rule disallowing procedure calls through immutable > references. As soon as we define such rules we get in some problems: > > > template TMin(T) { > T immutable min(immutable T x, immutable T y) { > return x < y ? x : y > } > } Why not: template TMin(T) { immutable T immutable min(immutable T x, immutable T y) { return x < y ? x : y; } } I'd assume it would be instantiated automatically. It would of course need along the normal version: template TMin(T) { T min(T x, T y) { return x < y ? x : y; } } And the overloading rules to pick the right version. > In my language I decided to drop side-effects because: it's very > expensive to track immutability and to provide two versions of the same > operations (e.g. a put method that changes the collection and a put method > that returns a new colection with the new value added). Also it'd very hard I'd assume that you wouldn't need two versions; if you only want to have an immutable and a mutable version of a class, just drop the procedures and leave the pure methods in the immutable version -- calling the procedure on the immutable version would be prohibited at compile time, nice and clean. Maybe you wanted something completely different? > Also it'd very hard to write "ensure" clauses on method's contracts that have side-effects (google for "old" expression and clone in comp.lang.eiffel). Shouldn't "ensure" clauses *not* have side-effects? Or did you mean "ensure" clauses of methods that have side-effects? Uh, could you give an example of one hard case? The thread you pointed at is probably the one with the subject "'old' + 'implies' = bug ?" (from 1996)? An interesting thread. "old" is a funny modifier, but one wonders if the semantical trouble arised because its real semantics ("will be evaluated before entering the function although it is mentioned and used only at the end") was swept under the rug while humming noble tunes about mathematical theories about contracts. And of course all the expressions in contracts are side-effectsless, at least if we don't cheat, right? *grin* IMHO all things that go with contracts tend to be a bit flaky. I just read (OOSC 2nd ed.) about abstract contracts that make it possible to actually tighten the preconditions in a deriving class, which shouldn't be possible -- but in this case is. (See the example about BOUNDED_STACK in the chapter about inheritance techniques.) I've not finished studying Eiffel's contract system in full but I do have the feeling that it's designed with a bit too much theory in mind -- I'd rather concentrate in making the constructs as usable and transparent as possible than create overly complicated (or overly simplified) theories and then watch them break in normal practical situations. > So, answering your question Antti, Eon is being rewritten by the last > time. Today it's a pure OOFPL, with a higher-order features, a simple syntax [snip] > standard library (collections, text, numerics, xml, io and networking) - most parts of the library are already specified (I have the method types and contracts and some implementations written in older versions of Eon). Best of luck with Eon, the type system pretty looks interesting. Must be worth the intellectual exercise, at least. (I must confess that I don't put much value on purely functional languages, but that could be just because I haven't studied them enough. Perhaps I shall be enlightened one day and write only Haskell after that.) To conclude, it seems that the efforts to combine mutable and immutable values tend to divert in different directions. Some (D) make the language simpler by forgetting constant types (almost) altogether. Some (Eon) go to the other extreme and make everything purely functional. Some (C++) march bravely forth and allow the complexity caused by having both the constant and the mutable. This makes wonder if there is at all a simple and beautiful language that would at the same time allow the constant and non-constant types to live peacefully together... -A |
August 26, 2003 Re: const / immutable [long] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antti Sykäri | Hi, Comments embedded. ----- Original Message ----- From: "Antti Sykäri" <jsykari@gamma.hut.fi> Newsgroups: D Sent: Monday, August 25, 2003 9:07 PM Subject: Re: const / immutable (was: C++ style const concept) [snip] > So this would be equivalent to C++'s const. (Without the possibility of subverting the type system, though.) > > I'll blabber a bit more about immutability below. > > > int sum(immutable int[] array) { > > int result = 0; > > for (int i = 0; i < array.length; i++) { > > result += array[i]; > > } > > return result; > > } > > > > > > The "immutable" modifier indicates that the parameter won't be changed > > by the operation body. Notice that a immutable array of type T is distinct > > from a array of type immutable T; this "immutable" modifier refers to the > > array immutability, not the array element type immutability. This type > > One interesting question regarding the semantics of immutable here is that what is the difference between immutable array of int and array of immutable int. Because the array can be viewed as consisting of its elements, doesn't its immutability imply the immutability of its elements and therefore make it equivalent to immutable array of immutable integers? If you have (using const to mark immutability): class Cell { private int value; this(int value) { this.value = value; } public void set(int value) { this.value = value; } public int immutable get() { return this.value; } } Cell c0 = new Cell(0), c1 = new Cell(1), c2 = new Cell(2), c3 = new Cell(3); Cell[] a0 = [c1, c2, c3]; // mutable array of mutable cell references immutable Cell[] a1 = [c1, c2, c3]; // mutable array of immutable cell references Cell[immutable] a2 = [c1, c2, c3]; // immutable array of mutable cell references const Cell[immutable] a3 = [c1, c2, c3]; // immutable array of immutable cell references a0[0].set(0); // ok, the a0[0] reference isn't immutable a0[0] = c2; // ok, the array isn't immutable a1[0].set(0); // compiler error, the a1[0] reference is immutable a1[0] = c2; // ok, a1 isn't immutable a2[0].set(0); // ok, the a2[0] reference isn't immutable a2[0] = c2; // compiler error, a2 is immutable a3[0].set(0); // compiler error, the a1[0] reference is immutable a3[0] = c2; // compiler error, a3 array is immutable Therefore I think that separating the immutability issue of the array vs. the element type leads to different semantics. Here we assume that "immutable A" is a supertype of "A". > It seems to depend on the kind of array and of the kind of the language it lives in. > > I'd assume (in pseudo-code notation) that array in a very "pure" modern, orthogonal, clean, type-safe etc. language (I'm putting some expectations on Eon you see after all those papers you've read :-) would have operations like: > > class immutable_array<T> > { > T get(int); // return by-value copy of element at i > int length(); > } > > class array<T> extends immutable_array<T> > { > void set(T, int) // set value at T to int > void length(int); > } > > This would mean in practice that having an immutable array would prohibit changing its elements, which would render them practically immutable. Immutable operations ("T immutable get(int)" and "int immutable length()") won't have side effects, but they may be changed by other objects/threads. But if you have a value type (like a number, matrix, etc.), a immutable struct type, you'll be safe, because the value isn't shared. Immutability tell us nothing about aliasing. > On the other hand, if the hypothetical language were less clean and had something as down-and-dirty as aliasing and references, what would an array then look like? Like this?: > > array<T> > { > T& get(int); // return a reference to element at i > // other properties > } > > Now what would the "immutable part of the interface" look like? Would it have the function "immutable T& get(int)"? But this would have the same signature, except for the hidden argument that contains the reference to the object the method is part of. Ah, there's the catch: > > immutable_array<T> > { > immutable T& immutable get(int); > // other properties > } > > And lo, we get C++ rules along with all the special cases and all. (There probably is some kind of logic to them after all, especially if you remove the warts induced by the C compatibility.) The problem in C++ is that they use "a[0] = 1;" meaning, get the address of the first element and let it be assignable, so you need two versions of array_get, one yielding const references and other yielding normal ones. IMO "a[0]" should call array_get and "a[0] = 1" should call array_set, just syntatic sugar. > If you have by-reference object, say of type T, what does "immutable T" mean? That you cannot change the reference to point to another object? Or that you can call only the pure methods of the object? Are the rules different if you have value objects? array<T> { T const get(int); int const length(); void set(int,T); void length(int); } int i1 = 1, i2 = 2, i3 = 3, i4 = 4; array<int> a1 = [i1, i2, i3, i4]; array<&int> a2 = [i1, i2, i3, i4]; const array<int> a3 = [i1, i2, i3, i4]; const array<&int> a4 = [i1, i2, i3, i4]; const array<const &int> a5 = [i1, i2, i3, i4]; a1.set(0, i3); // ok a2.set(0, &i3); // ok a2.get(0) = 5; // ok a3.set(0, i3); // compiler error a4.set(0, &i3); // compiler error, can't mutate the array. a4.get(0) = 8; // ok a5.set(0, &i3); // compiler error a5.get(0) = 8; // compiler error It's a bit tricky, but the const qualifies the reference type too. > > modifier should be carried through assignment to avoid cheating, or else the > > operation could store the value in a mutable variable and break the immutability. There's a second way to use the "immutable" modifier: to define pure functions. > > At this phase I must point out that the semantics seem awfully familiar from C++, only the names differ: > > method - member function > immutable - const > pure function - const member function > > > > An "immutable" operation won't cause side effects, so inside a class you > > can separate functions from procedures to ensure immutability. The compiler > > could enforce this rule disallowing procedure calls through immutable references. As soon as we define such rules we get in some problems: > > > > > > template TMin(T) { > > T immutable min(immutable T x, immutable T y) { > > return x < y ? x : y > > } > > } > > Why not: > > template TMin(T) { > immutable T immutable min(immutable T x, immutable T y) { > return x < y ? x : y; > } > } > > I'd assume it would be instantiated automatically. It would of course need along the normal version: > > template TMin(T) { > T min(T x, T y) { return x < y ? x : y; } > } > > And the overloading rules to pick the right version. Hmmm, perhaps you've meant: template TMin(T) and template TMin(immutable T) But the issue here is that immutability, when you're dealing with generic types, generates some dependency between the parameters and the result types. Sometimes only one parameter will define the const issue, sometimes more than one. If you make a templated function to find statistical results from a array (min, max, average, etc.) you need a way to ensure that these functions won't: mutate the array, won't mutate the array elements, the return types will have the same immutability status (yes or no) that the array element type. In your example the TMin version for mutable parameters allowed the min function to change them through any mutating methods they had. > > In my language I decided to drop side-effects because: it's very > > expensive to track immutability and to provide two versions of the same > > operations (e.g. a put method that changes the collection and a put method > > that returns a new colection with the new value added). Also it'd very hard > > I'd assume that you wouldn't need two versions; if you only want to have an immutable and a mutable version of a class, just drop the procedures and leave the pure methods in the immutable version -- calling the procedure on the immutable version would be prohibited at compile time, nice and clean. > > Maybe you wanted something completely different? I was talking about the issue I mentioned in my last (statistical) example. You need to write things like: template stats(T) { const_if_array_type_is_const T find_min(const T [const] array) { ... } } That can be very awkward. The problem here is: the result type immutability depends on the array element type immutability, but we must be able to ensure that the array element type won't be changed by the function. These examples may be simple, but when you have more classes, it's hard to track immutability dependencies. Also immutability is one level only: class Cell(T) { private T value; this(T value) { this.value = value; } public void set(T value) { this.value = value; } public T immutable get() { return this.value; } } Cell(int) c1 = new Cell(int)(1); const Cell(Cell(int)) c2 = new Cell(Cell(int))(c1); const Cell(const Cell(Cell(int))) c3 = new Cell(const Cell(Cell(int)))(c2); c3.set(c2); // compiler error; c3.get().set(c1); // compiler error; c3.get().get().set(0); // ok; With "Cell" classes it's easy, because you can specify the internal elements immutability status, but some classes, like "Department" that contains "Employee", are harder, because you may mutate "Employee" through a "immutable Department" reference, because you must be able to tell that the "Employee get(String)" won't mutate the "Department", but when called via a "immutable Deparment" reference should yield a "immutable Employee", but when calles via a common "Department" reference should yield a common "Employee". If we include the implicit this parameter in the type we may be able to do this (somehow, but's awkward): immutable Employee immutable get(immutable Department this, immutable String name); Employee immutable get(Department this, immutable String, name); where the "immutable" qualifier before the "get" means the this reference won't be mutated. Some issues regarding mutability are discussed here <http://lambda.weblogs.com/discuss/msgReader$8378?mode=topic&y=2003&m=8&d=25 > Tim Sweeney has some pretty good thoughts. > > Also it'd very hard to write "ensure" clauses on method's contracts that have side-effects (google for "old" expression and clone in comp.lang.eiffel). > > Shouldn't "ensure" clauses *not* have side-effects? Or did you mean "ensure" clauses of methods that have side-effects? > > Uh, could you give an example of one hard case? Sorry I meant: 'Also it's very hard to write "ensure" clauses on contracts of methods that have side-effects (i.e. methods that mutate the instance)'. Set(T) { void remove(T item) out () { if (old this.contains(item)) { assert(this.size + 1 == (old this.size())); } assert(!this.contains(item)); // so far so good. for (T each; old this) { if (each != item) { assert(this.contains(item)); } } /* ops, it won't work because the "old this" expression will yield a reference * to the this object, and here it's already mutated. The correct expression is * "(old this.clone())" ensuring that we get a snapshot of the this object from * before the method execution. */ }; } This is the simplest case of "old" expressions in mutating methods. There's more fun. Check the Eiffel kernel classes' contracts for more details. [snip] > > So, answering your question Antti, Eon is being rewritten by the last > > time. Today it's a pure OOFPL, with a higher-order features, a simple syntax > [snip] > > standard library (collections, text, numerics, xml, io and networking) - > > most parts of the library are already specified (I have the method types and > > contracts and some implementations written in older versions of Eon). > > Best of luck with Eon, the type system pretty looks interesting. Must be worth the intellectual exercise, at least. (I must confess that I don't put much value on purely functional languages, but that could be just because I haven't studied them enough. Perhaps I shall be enlightened one day and write only Haskell after that.) > > To conclude, it seems that the efforts to combine mutable and immutable values tend to divert in different directions. Some (D) make the language simpler by forgetting constant types (almost) altogether. Some (Eon) go to the other extreme and make everything purely functional. Some (C++) march bravely forth and allow the complexity caused by having both the constant and the mutable. This makes wonder if there is at all a simple and beautiful language that would at the same time allow the constant and non-constant types to live peacefully together... > > -A As I (hopefully) showed you, the immutability issue is very tricky. Once we assume that everything can mutate we need to state everything that won't change everytime. There are lots of hard work on this area, some propose a "mutates" clause on methods, to define which object's parts will be different after a method call. My test of Eon features was writing libraries with the proposed syntax and semantics. When Eon had side-effects as the norm and keywords to ensure immutability the code become very, very verbose. Once I dropped the side-effects, I could concentrate on behaviour and isolate the changes in Monads. IME it's almost impossible to come with a readable, concise and comprehensive notation for immutability vs. mutability. Best regards, Daniel Yokomiso. "I didn't attend the funeral, but I sent a nice letter saying I approved of it." - Mark Twain --- Outgoing mail is certified Virus Free. Checked by AVG anti-virus system (http://www.grisoft.com). Version: 6.0.512 / Virus Database: 309 - Release Date: 20/8/2003 |
August 26, 2003 Re: const / immutable [long] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | "Daniel Yokomiso" <daniel_yokomiso@yahoo.com.br> wrote in message news:bifhh6$1hb7$1@digitaldaemon.com... > Therefore I think that separating the immutability issue of the array > vs. the element type leads to different semantics. Here we assume that > "immutable A" is a supertype of "A". This seems logical. > Immutable operations ("T immutable get(int)" and "int immutable > length()") won't have side effects, but they may be changed by other > objects/threads. But if you have a value type (like a number, matrix, etc.), > a immutable struct type, you'll be safe, because the value isn't shared. Immutability tell us nothing about aliasing. Not having side effects allows one set of optimizations. I would like the language to include something like "values changed by other threads *can* show up as changed to a thread which uses those values, however, changes are not *guaranteed* to show up unless the values are marked volatile." This would allow the compiler to keep more values in registers. Not sure what to do about called functions. There is something extremely nice about knowing for sure that nothing will ever modify a structure. > The problem in C++ is that they use "a[0] = 1;" meaning, get the address > of the first element and let it be assignable, so you need two versions of array_get, one yielding const references and other yielding normal ones. IMO > "a[0]" should call array_get and "a[0] = 1" should call array_set, just syntatic sugar. Basically properties. > But the issue here is that immutability, when you're dealing with > generic types, generates some dependency between the parameters and the > result types. Sometimes only one parameter will define the const issue, > sometimes more than one. If you make a templated function to find > statistical results from a array (min, max, average, etc.) you need a way to > ensure that these functions won't: mutate the array, won't mutate the array > elements, the return types will have the same immutability status (yes or no) that the array element type. In your example the TMin version for mutable parameters allowed the min function to change them through any mutating methods they had. This is what I don't understand. Why should Min return an immutable value? Perhaps it can return a copy of a value that can be mutated. That's probably lower performance though. Is this the only reason? Sean |
August 26, 2003 Re: const / immutable [long] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | > > But the issue here is that immutability, when you're dealing with > generic types, generates some dependency between the parameters and the > result types. Sometimes only one parameter will define the const issue, > sometimes more than one. If you make a templated function to find > statistical results from a array (min, max, average, etc.) you need a way to > ensure that these functions won't: mutate the array, won't mutate the array > elements, the return types will have the same immutability status (yes or no) that the array element type. In your example the TMin version for mutable parameters allowed the min function to change them through any mutating methods they had. > > > > > In my language I decided to drop side-effects because: it's very > > > expensive to track immutability and to provide two versions of the same > > > operations (e.g. a put method that changes the collection and a put > method > > > that returns a new colection with the new value added). Also it'd very > hard > > > > I'd assume that you wouldn't need two versions; if you only want to have an immutable and a mutable version of a class, just drop the procedures and leave the pure methods in the immutable version -- calling the procedure on the immutable version would be prohibited at compile time, nice and clean. > > > > Maybe you wanted something completely different? > > > I was talking about the issue I mentioned in my last (statistical) > example. You need to write things like: > > > template stats(T) { > const_if_array_type_is_const T find_min(const T [const] array) { ... } > } > > > That can be very awkward. The problem here is: the result type > immutability depends on the array element type immutability, but we must be > able to ensure that the array element type won't be changed by the function. > These examples may be simple, but when you have more classes, it's hard to track immutability dependencies. Also immutability is one level only: > > maybe something like that would be OK: template stats(T) { constnessof(array) T find_min(const T [const] array) } where we can indicate what to check for constness. The list can include this for member functions and for complex type we might allows a syntax similar to the one used for specialisation constnessof(array : pattern) where patern would include the optional const or alternatively a syntax where we could name modifier: For the sample I will uses # symbol. There are probably better alternative. Makes your suggestions!!! template stats(T) { constnessof(c1, c2) T find_min(const#c1 T [const#c2] array) } For non-virtual methods, we could support automatic constness detection which should not be hard for the compiler to implement (about the same complexity as generating an error in C++ when constness is not respected). template stats(T) { autoconst T find_min(const T [const] array) } where const would be added if required. This would be usefull for generic code like the TMin template or for containers that wants to return constant reference to their elements if they are themself const... Automatic modifier would also have other uses that for constness support. They would be usefull for function calling convention for example (I don't know how those specifier are handled in D - that is the equivalent of cdecl, fastcall, pascal and similar in C++). For ex. in C++, mem_fun and related classes and functions only works when the default calling convention is used... but ideally the same code should be usable with any calling convention. |
August 26, 2003 Re: const / immutable (was: C++ style const concept) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Antti Sykäri | Antti Sykäri <jsykari@gamma.hut.fi> wrote in news:slrnbk5c5l.4q6.jsykari@pulu.hut.fi: > Java, a blatant mockery of static typing, solves the "const" by providing a view to the container (via java.util.Collections.unmodifiableCollection or similar) which looks exactly like the original, but throws an UnsupportedOperationException when one tries to modify it. No wonder there's a project trying to bring "const" to Java (http://pag.lcs.mit.edu/constjava/). [...] ConstJava looks like a straight adaption of C++'s const notion. They just added the useful notion (for Strings etc.) of const classes. The paper (http://pag.lcs.mit.edu/constjava/ConstJava.pdf) talks about a runtime exception for const_casts, but I didn't find any details. Wonder how this works. Farmer. |
August 27, 2003 Re: const / immutable [long] | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Yokomiso | Ok, let's drop immutable and just talk about const. In article <bifhh6$1hb7$1@digitaldaemon.com>, Daniel Yokomiso wrote: > But the issue here is that immutability, when you're dealing with > generic types, generates some dependency between the parameters and the > result types. Sometimes only one parameter will define the const issue, > sometimes more than one. If you make a templated function to find > statistical results from a array (min, max, average, etc.) you need a way to > ensure that these functions won't: mutate the array, won't mutate the array > elements, the return types will have the same immutability status (yes or > no) that the array element type. In your example the TMin version for > mutable parameters allowed the min function to change them through any > mutating methods they had. > [snip] >> Maybe you wanted something completely different? > > > I was talking about the issue I mentioned in my last (statistical) > example. You need to write things like: > > > template stats(T) { > const_if_array_type_is_const T find_min(const T [const] array) { ... } > } > > > That can be very awkward. The problem here is: the result type > immutability depends on the array element type immutability, but we must be > able to ensure that the array element type won't be changed by the function. Okay, now I think I have an idea about the fundamental problem. The min() function can be used as an example of the general idea. The function must return one of its arguments, whether mutable or immutable. At the same time it must provide the guarantee that it won't modify its arguments -- we'd like the type system to ensure that the only thing that "min(T, T)" is allowed to do is to pass one of its arguments back to the caller. One way to ensure this is to make its arguments const; but then you'd have to make its return type const, as well, or the following wouldn't compile: (Let's leave the bodies away -- everybody probably knows what "min" looks like if he or she has read this far :) I'll drop the template syntax because it has nothing to do with const really. Assume that T is an int or actually, reference type would be better to illustrate the const semantics. const T min(const T x, const T y); This is inacceptable, because sometimes we'd like to use min on non-const arguments; therefore we could solve the problem with overloading: T min(T x, T y); const T min(const T x, const T y); And we need to have C++-like rules to disambiguate between the calls, or maybe now we could have some use for templates: implicit template function (const T) { const T min(const T x, const T y); } implicit template function (T) { T min(T x, T y); } Another way would be unsafe and dirty but I'll still include it for completeness: T min(const T x, const T y) { T mutable_x = const_cast<T>(x), mutable_y = const_cast<T>(y); return x < y ? mutable_x : mutable_y; } However, this would require the caller to promise not to touch the result if one of the originals were const in the first place. Ok, pretty ugly idea but its actually a prelude to this solution: Don't use "const" as a type modifier at all, but merely as a way of documenting the function's behavior (as in "I won't change this parameter"). Enter contracts. Like so: T min(const T x, const T y) { // implementation... } out() { // mathematical properties of min(x,y) assert(result <= x); assert(result <= y); // it's one of them assert(result === x || result === y); // I didn't change my parameters, I promise; these would be // generated by "const" assert(old x == x); assert(old y == y); // Here we would actually have to rely on // deep_clone or similar, as you mentioned below. Tricky notion, // comparing something that exists now to something that existed // earlier in time } Side note: Don't you just love when the function's postconditions actually describe perfectly what the function does! Reminds of me of the good old combinator function in purely functional languages. It takes a function of type ('a -> 'b) and a function of type ('b -> 'c) and returns a function of type ('a -> 'c). Now what could a function of this type do? The notation "'something" means generic type parameter, so it cannot assume anything about its types. Well, what else can it do, then, than create a function that first calls the first function and then calls the second function on the result? So the function's type is a complete description of the function itself. Back to the min() problem. Ideas only keep getting wilder... well, do you remember how we did it in C times: Macro! Of course! #define min(x, y) ((x) < (y) ? (x) : (y)) This actually works better (except for the side-effects of call-by-name parameter passing) than any of its C++ equivalents, since you can do things like int x = 5; const float y = 1.0; float z = min(x, y); No need for different "const" methods, overloading or anything! Amazing! Compare this with what Andrei Alexandrescu did to get the (nearly) similar effect with templates: http://www.cuj.com/documents/s=7996/cujcexp1904alexandr/ By the way, he has an interesting comment: "Minimum and maximum are two simple concepts present both in math and in the real life. If a programming language is unable to express simple concepts of math and life, then there is something deeply wrong with that language." Anyway - macros seem to work. Why is this possible? Well, because min() is not an actual function call but merely a textual substitution. Well, what language feature was introduced in C++ (and later, in C) to get rid of a certain usage of macros? Taking Andrei's challenge, let's bring forth the final solution: inline functions. I wouldn't mean to use inline functions as a means to perform premature optimization, a purpose which programmers are often accused of misusing. But I simply would want to take advantage of the fact that inline functions can "see" the context where they are used, and therefore they would also be able to provide different kinds of code than usual functions. Actually, after an inline function were expanded several times, the compiler could gather them to a single (if possible) function and optimize the inlined code to a function call :) One thing that inline functions could provide would be determining the return type based on argument types. In fact, the inline function could work directly on the symbolic environment of the caller; assume inline Symbol min(Symbol x, Symbol y) { return x < y ? x : y; } This would expand similarly to the macro: void g(int x, float y) { float z = min(x, y); // expands into: // float z = x < y ? x : y; } It would have to have different specialisations; for non-symbol expressions, the following would be expanded: inline T min(T x, T y) { // as usual } Inline functions would of course have to have some kind of template-like implicit instantiation mechanism (which D templates is soon going to get, I hope) and ability to specialize on different types. And they could naturally be used to implement variable-sized argument lists type-safely... Hmm, I think I got sidetracked a bit. Could be time for sleep. > Some issues regarding mutability are discussed here ><http://lambda.weblogs.com/discuss/msgReader$8378?mode=topic&y=2003&m=8&d=25 >> Tim Sweeney has some pretty good thoughts. An interesting discussion. BTW, the guy who he was talking with, Vesa Karvonen, I used to work with some time ago. He's a smart guy; taught me a lot about programming. It's a small world, and particularly small in the area of functional languages and type theory :) >> Uh, could you give an example of one hard case? > > Sorry I meant: 'Also it's very hard to write "ensure" clauses on > contracts of methods that have side-effects (i.e. methods that mutate the > instance)'. > > Set(T) { > void remove(T item) > out () { > if (old this.contains(item)) { > assert(this.size + 1 == (old this.size())); > } > assert(!this.contains(item)); > // so far so good. > for (T each; old this) { > if (each != item) { > assert(this.contains(item)); > } > } > /* ops, it won't work because the "old this" expression will yield a > reference > * to the this object, and here it's already mutated. The correct expression > is > * "(old this.clone())" ensuring that we get a snapshot of the this object > from > * before the method execution. > */ > }; > } Well, that's what you get if you really want to compare two sets, only one of which is available at a time. I'd really just leave it, settle with simpler preconditions and place some trust on the programmer... I wouldn't find very complicated examples in Eiffel's libraries, either; in particular, no clone()'s in old expressions. I looked at, for example, the equivalent BINARY_SEARCH_TREE_SET at http://docs.eiffel.com/libraries/base/reference/structures/set/binary_search _tree_set.html#f_prune which has the similar postcondition removed_count_change: old has (v) implies (count = old count - 1) but does not have the last check. -Antti |
Copyright © 1999-2021 by the D Language Foundation