Re: Any chance to call Tango as Extended Standard Library
aarti_pl wrote: first - last advance - retreat My preference: head - rhead next - rnext (or advance - radvance) The purpose of retreat and toe is to allow reverse iteration. retreat in not the opposite of advance/next, it's the same operation applied to the other end of the range. So why not make this explicit by prefixing an 'r' to these reverse iteration functions? -- Rainer Deyke - rain...@eldwood.com
Re: Please vote once and for good: range operations
Daniel Keep wrote: advanceFront -- advance the front element by one retreatBack -- retreat the back element by one I like this. Verb instead of noun for action, unambiguous about which end moves in which direction, and it's symmetrical. nonEmpty-- as Walter one espoused: negative = bad, positive = good :D I don't like this. !empty reads better than nonEmpty, and empty reads much better than !nonEmpty. -- Rainer Deyke - rain...@eldwood.com
Re: If !in is inconsistent because of bool/pointer, then so is !
downs wrote: This is NOT a reason against !in. In fact, this so-called inconsistency is already present in the language. If we remember, !pointer already transforms it into a boolean, so it would actually be more consistent if !in changed the return type to bool. I agree. 'a != b' is short for '!(a == b)'. 'a !is b' is short for '!(a in b)'. For consistency, 'a !in b ' should be short for '!(a in b)'. I'd even go so far as to say that 'a !+ b' should be short for '!(a + b)', although I can't think of a use for the '!+' operator. a !op b == !(a op b): simple, consistent pattern. a !op b == !(a op b), but only for op in some limited set that doesn't include all operators with which you might want to use the pattern: less consistent; requires memorization. -- Rainer Deyke - rain...@eldwood.com
Re: If !in is inconsistent because of bool/pointer, then so is !
downs wrote: A large part of the case for !in is that you can pronounce it a *not in* b. !+, on the other hand, would be .. what? a not plus b? does that mean a - b? :) It's a question of consistent patterns versus special cases. If 'a !op b == !(a op b)', then the parser can rewrite all 'a !op b' expressions as '!(a op b)' in a single place, without looking at what op is. (Of course '!=' (as the opposite of '==' as opposed to '=') is already a special case, so perhaps defining the '!op' operators individually is unavoidable. 'a !== b' as '!(a == b)' would work, but 'a != b' as '!(a = b)' would be very weird and inconsistent with other languages.) I'm not suggesting that anybody should actually /use/ the '!+' operator, even if it was defined. That would be horrible. -- Rainer Deyke - rain...@eldwood.com
Re: If !in is inconsistent because of bool/pointer, then so is !
Bill Baxter wrote: Note that D already has things like !. But quoth the spec: For floating point comparison operators, (a !op b) is *NOT* the same as !(a op b). [emphasis added] I had to check the spec for the difference. 'a ! b' and '!(a b)' /are/ equivalent in the sense that '(a ! b) == !(a b)' for any values of 'a' and 'b'. The vast majority of the time, the expressions 'a ! b' and '!(a b)' /are/ interchangeable. The difference is that '!(a b)' sets a global exception state if either operand is NaN, while 'a ! b' does not. This is, in my opinion, a significant design error in the language. The difference between '!(a b)' and 'a ! b' is not obvious. There is nothing about the operator '' that suggests that it should set a global exception state, and there is nothing about '!' that suggests that it should /not/ set a global exception state. (Is global state for error reporting ever a good idea in a high-level language?) It also adds awkward expressions to the language, not just in the form '!(a b)', but in the form '!(a ! b)'. -- Rainer Deyke - rain...@eldwood.com
Re: If !in is inconsistent because of bool/pointer, then so is !
Daniel Keep wrote: Rainer Deyke wrote: This is, in my opinion, a significant design error in the language. The difference between '!(a b)' and 'a ! b' is not obvious. There is nothing about the operator '' that suggests that it should set a global exception state, and there is nothing about '!' that suggests that it should /not/ set a global exception state. (Is global state for error reporting ever a good idea in a high-level language?) It also adds awkward expressions to the language, not just in the form '!(a b)', but in the form '!(a ! b)'. I believe this is, or is the result of, an aspect of IEEE floating point. I don't have a copy of the IEEE floating point standard, but I strongly suspect it would have allowed syntax like: a b // sets global state a ! b // sets global state less_than_no_state(a, b) // does not set global state !less_than_no_state(a, b) // does not set global state Or: a b // does not set global state a ! b // does not set global state less_than_set_state(a, b) // sets global state !less_than_set_state(a, b) // sets global state Or: a b // sets global state a ! b // sets global state a [] b // does not set global state a [!] b // does not set global state Or any number of other syntax choices, all less confusing than the syntax actually used. This is assuming we need two sets of comparison operators, one of which uses global state to report NaN operands and one which does not. I'm not convinced that this is the case. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Michel Fortin wrote: Polymorphism doesn't work very well while passing objects by value, even in C++. This is called the slicing problem. I have heard about the slicing problem. I know what it is. But in all my years of using C++ as my primary language, I have never actually encountered it. I don't believe it actually exists. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Andrei Alexandrescu wrote: How long have you used C++? My first serious C++ project was started in 1997. This is not a tendentious question. In the recent years, the advent of quality smart pointers, an increased scrutiny of use of inheritance, and the teaching of idioms associated with reference types has greatly diminished slicing accidents. But in the olden days, people were inheriting value types left and right because it was the emperor's new clothes. Slicing is caused by naive copying of polymorphic types, which is only tangentially related to inheriting value types. Example 1 (C++): struct point2d { int x, int y; }; struct point3d : public point2d { int z; } 'point2d' is a non-polymorphic value type. Slicing behavior exists, is intentional, and does not invalidate any invariants. Example 2 (C++): class data_source { public: virtual char read_byte() = 0; }; class input_file : public data_source { public: char read_byte() = 0; }; 'input_file' is a value type that implements an interface. No slicing problem exists because 'data_source' is an abstract type. Example 3 (C++): class person { public: person(person const org) { assert(typeid(org) == typeid(*this)); } virtual ~person() {} }; class avatar : public person, public deity { }; 'person' and 'avatar' are value types that can be used polymorphically. In any context 'person' may be either a reference to an instance of class 'person' or a polymorphic reference to an instance of any class that inherits from 'person'. Unfortunately the C++ type system does not distinguish between these two uses. It is an error (checked at runtime) to construct a new 'person' from a polymorphic reference, but any other use of 'person' as a value type or a polymorphic type is valid. No slicing problem exists except through user error. Example 4 (D): class RubberChicken { RubberChicken dup() { return new RubberChicken(); } } class RubberChickenWithAPulleyInTheMiddle { private Pulley pulley; // Forget to override 'dup'. } Here 'RubberChicken' is a reference type, but due to programmer error, the slicing problem still exists. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Andrei Alexandrescu wrote: The slicing problem exists in spades in this example, or better put its converse (your code will fire asserts when it shouldn't). The reason is rather subtle, so please try the code out to edify yourself. You're referring to the automatically generated copy constructor in class 'avatar' which calls the copy constructor in class 'person', right? That's a bug in my silly untested example code, but it's not the slicing problem. Fix 1: class person { person(person const org, bool allow_slicing = false) { assert(allow_slicing || typeid(org) == typeid(*this)); } }; class avatar : public person, public deity { avatar(avatar const org, bool allow_slicing = false) : person(org, true) { } }; Fix 2: Trust the programmer to pay attention and remove the silly unnecessary assertion from class 'person'. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Andrei Alexandrescu wrote: So the problem exists since you are trusting the programmer to avoid it. The slicing problem exists in the sense that it is possible for a bad programmer to accidentally slice an object. However: - This is not a problem with the language, but an avoidable programmer error in the language. - There are far more common programmer errors in C++ against which D does not guard. For example, dangling pointers to stack variables that have gone out of scope. - D's response to this perceived problem is far too heavy-handed, because it disallows useful correct code. - I would even say that the reference type classes in D lead to more problems. It is very easy for two objects in D to accidentally share internal state, something that is impossible with value types. - Even if the slicing problem were a major issue in C++, it should be possible to partially or totally fix it at the language level in D without sacrificing inheritance for value types. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Christopher Wright wrote: - I would even say that the reference type classes in D lead to more problems. It is very easy for two objects in D to accidentally share internal state, something that is impossible with value types. struct A { int* i; } A a, b; int* i = new int; a.i = i; b.i = i; Hey look, they share internal state! That's not accidental. You did that on purpose. What mechanism for sharing state is available to by-reference objects but not by-value objects? struct C { T t; } C c1; C c2 = c1; If T was a reference type, 'c1' and 'c2' now share state, and it's up to the programmer to write code to prevent this. Moreover, the D language doesn't even give a hint as to whether T or a reference type or a value type. If T is a template parameter, it could be both. What I'd really like to see is a hybrid of struct and class that has the RAII and value semantics of a struct, but the polymorphism of classes. polymorphic_struct A { } polymorphic_struct B : A { } { B b; A a = b; // Creates a true non-sliced copy of 'b'. } // Destructors are called when the variables go out of scope. At the implementation level, the objects could be placed on the heap unless the compiler determines that this is not necessary. The goal is not performance, but consistent value semantics throughout the language. The alternative is to work backwards and create a wrapper for classes to give them value semantics: struct value_semantics(T) { this(T v) { this.value = v; } this(this) { this.value = this.value.dup; } ~this() { delete this.v; } T opDot() { return this; } T value; } -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Christopher Wright wrote: Rainer Deyke wrote: What mechanism for sharing state is available to by-reference objects but not by-value objects? struct C { T t; } C c1; C c2 = c1; If T was a reference type, 'c1' and 'c2' now share state, and it's up to the programmer to write code to prevent this. Moreover, the D language doesn't even give a hint as to whether T or a reference type or a value type. If T is a template parameter, it could be both. Okay, reference types don't have value semantics. That's exactly what I would expect. That sounds tautological, but many newcomers to D are surprised by reference semantics when they expected value semantics. Why is it a problem? Because reference types don't play well with RAII types. Because unintentional object aliasing a problem orders of magnitude more common than unintentional object slicing, and at least an order of magnitude harder to detect and fix. Because you can't correctly duplicate a multi-dimensional dynamic array in D by using the built-in 'dup' property. Because having two syntactically similar ways of creating user-defined types with different features sets and different semantics is confusing, especially if you later decide that you need a struct to be a class and all your copies turn into reference or vice versa. Because there is no explicit builtin copying mechanism for classes? If there were such, would it be a problem if copying were not the default? What exactly do you have in mind? Would it allow classes to correctly work with RAII types? Would it prevent unintentional object aliasing? Would it allow multi-dimensional arrays to be correctly duplicated? Would it remove the need for both classes and structs in the language, or at least the need to switch between the two? -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Daniel Keep wrote: It's not a bug. There are differences between value types and reference types. Just like how there are differences between atomic types and aggregate types. Or constant types and mutable types. This is a bug: struct MathematicalVector { this(int size) { this.values = new T[size]; } // No copy constructor. // Insert standard mathematical operators here. private T[] values; // Implementation detail. } I want MathematicalVector to be a value type (which is why I declared it as a struct). However, it doesn't behave a value type because I forgot to write the copy constructor. D doesn't cause the bug, but it certainly makes it easier to accidentally write this kind of bug. By contrast, the compiler-generated copy constructors in C++ usually do the right thing for all but a handful of low-level resource-management classes. Which is not to say that C++ doesn't have problems of its own - clearly it does, or I wouldn't be looking at D. -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Tom S wrote: Rainer Deyke wrote: Because you can't correctly duplicate a multi-dimensional dynamic array in D by using the built-in 'dup' property. Simply because shallow copies are the default. Usually you can't even have automatic deep copies. What if the array was supposed to be malloc'd or put in a contiguous memory block taken from a memory cache? A .dup that made a deep copy of it would introduce even a larger problem (since it would seem correct) than remembering that dups/struct copies are shallow by default and you need special code to do otherwise. C++ has a way of specifying how deep you want your copies to be at the declaration point: std::vectorboost::shared_ptrstd::vectorint v1, v2; v2 = v1; // Shallow copy. std::vectorstd::vectorint v3, v4; v4 = v3; // Deep copy. Granted, C++ is a mess and you could do something similar in D by wrapping the reference in a value_semantics wrapper. value_semantics!(value_semantics!(int[])[]) v5, v6; v6 = v5; // Deep copy due to the value_semantics templated struct. Unfortunately this doesn't solve the RAII problem: if value_semantics!(T) deletes the contained reference in its destructor, then it is no longer safe to embed a value_semantics!(T) variable in memory managed by the garbage collector. Would it remove the need for both classes and structs in the language, It's not a bug, it's a feature. Having both value types and reference types in the language is (arguably) a feature. Having no syntactic distinction between them at the point of use is at best a misfeature. Forcing programmers to change their value types into reference types when they need polymorphism or inheritance, even though safe polymorphic value types are possible, is clearly a misfeature. [...] change my scanf calls [...] Are you serious? -- Rainer Deyke - rain...@eldwood.com
Re: Old problem with performance
Andrei Alexandrescu wrote: C++ ctors won't do the right thing if you use pointers, which is the moral equivalent of using T[] inside MathematicalVector. If you refer to std::vector instead, then that's a carefully-defined type that does have the right copy constructor defined. So where's the D equivalent of std::vector? Phobos has nothing. Tango has containers, but they are also reference types. I was under the impression that native arrays were intended to be used directly in D, something I rarely do in C++. Certainly neither of the standard libraries has any hesitation about passing unadorned dynamic arrays around. -- Rainer Deyke - rain...@eldwood.com
Re: ref?
Jerry Quinn wrote: If you want inheritance, copy semantics are an issue. For example, if you have struct A : B, and an array of B, you can't put an A in it, since there's not enough space for an A (unless A adds 0 storage). If you have an array of A, inheritance isn't really buying you anything over having a B as the first member of A. Any place where you'd want to use A as a B, you can get to the member B struct directly. There are really three separate issues here: copy policy, storage policy, and clean-up policy. Copy policy: what are the semantics of 'a = b;'? Reference semantics: 'a' becomes a reference to the same object as 'b'. Value semantics: The object 'b' is copied into 'a'. Storage policy: given 'A a;', where is the object stored? Direct storage: Directly in the variable 'a'. Padded direct storage: Directly in the variable 'a', but with enough padding to also store an object of any subtype of 'A'. Requires that the full set of the subtypes of 'A' be known at compile time. Heap storage: The object is stored on the heap; the variable 'a' merely contains a pointer. Clean-up policy: given 'A a;', when is 'a''s destructor called? RAII: Immediately when 'a' goes out of scope. The object still exists at this point. Garbage collection: Sometime after the last reference to the object is no longer reachable from any global or stack variable. By the time the destructor is called, other objects referenced by the object may already have been destroyed. These three policies are mostly conceptually orthogonal, although garbage collection requires heap storage. Interesting combinations include: Reference semantics, heap storage, garbage collection: Classes in D. Value semantics, direct storage, RAII: Structs in D. Value semantics, padded direct storage, RAII: One possible approach to value types with inheritance but without slicing. Value semantics, heap storage, RAII: Another possible approach to value types with inheritance but without slicing. Reference semantics, heap storage, RAII (with reference counting): Reference types with destructors that actually work. -- Rainer Deyke - rain...@eldwood.com
Re: Beginning with D
Prestidigitator wrote: Is D as good at game programming as C++? Also, would it be better to use 1.0 or 2.0? My opinion: D 1.0 is, on the whole, worse than C++. D 2.0 is shaping up to be, on the whole, better than C++. However, D 2.0 is unstable to the point of being unusable at the moment. Stick to C++ for now, but come back in a year or two when D 2.0 has had time to stabilize. -- Rainer Deyke - rain...@eldwood.com
Re: Beginning with D
grauzone wrote: Going back from D to C++ also feels like stepping back, because C++ doesn't natively support garbage collection. While I admit that reference counting is better for heavy resources (like file handles), for small memory objects tracing garbage collection is actually more efficient and less problematic. For example, reference counting can't deal with cycles of garbage. Between cycle-breaking and support for heavy resources, I'd take the latter over the former any day. IME heavy resources are very common, and cycles are relatively rare. This is especially true for game programming. I have many options for handling cycles in C++. I have no reasonable options for handling heavy resource in D1. And what's so great about wrapping every pointer into a smartpointer? Consistent syntax? Having a choice of smart pointers? Honestly, I can't see a way to avoid smart pointers even in D2. A language like Python may not need them, but then Python has reference counting and destructors that actually work in addition to garbage collection. And weak references. -- Rainer Deyke - rain...@eldwood.com
Re: First class lazy Interval
Bill Baxter wrote: 4..$ 4u..$ etc? Aside from the inconsistent meaning of $, you still can't have an inclusive range [a, b], where 'a' and 'b' are not only known at runtime, without treating 'b == int.max' as a special case. -- Rainer Deyke - rain...@eldwood.com
Re: std.locale
Georg Wrede wrote: Let's split this into two separate issues, the console and the GUI. The GUI is aware of your preferences. You don't use writefln with the GUI. You use the GUI API for any I/O, right? There's a third faction: graphical apps that don't use the underlying GUI API. Most games fall in this category. When writing cross-platform apps (whether gui, non-gui-but-graphical, or console), you need some layer of abstraction over the underlying platform localization API. This abstraction can be provided by the programming language, or a third-party library. A proper internationalisation would mean that the Chinese could use the console, and all character mode apps in Chinese. Problem is, there simply aren't enough pixels on many consoles to render the Chinese character set. I have Windows configured to use a Japanese text encoding for command windows. I can and do run Japanese console applications, but console applications that assume CP437 or Latin-1 don't work for me. -- Rainer Deyke - rain...@eldwood.com
Re: std.locale
Sergey Gromov wrote: To actually solve this problem the default exception handler must be fixed to convert any UTF-8 into the current OEM code page before printing. It would also help if default stdin and stdout performed such a conversion. No, stdin/stdout *must* perform this conversion. It is a serious bug if they don't. The conversion cannot be performed at any other level. D uses unicode internally. The console uses a specific encoding. Therefore all data passing between D and the console must be encoded/decoded. -- Rainer Deyke - rain...@eldwood.com
Re: Null references (oh no, not again!)
Daniel Keep wrote: The point was that these were identified as being responsible for the majority of the bugs in a large, real-world code base. Clearly #2 and #3 are common enough and cause enough issues to have made the list. A sample size of one doesn't mean much. In my experience, none of those four factors account for a significant amount of bugs, since all of them (except integer overflow) can be caught without too much effort through the copious use of assertions. I'd still prefer non-nullable references to be the default though. Writing an assertion for every non-nullable reference argument for every function is tedious. -- Rainer Deyke - rain...@eldwood.com
Re: Null references (oh no, not again!)
Walter Bright wrote: It's also quite unnecessary. The hardware will do it for you, and the debugger will tell you where it is. By the same reasoning, unit tests are unnecessary. The end user tells you that there's a bug, and the manually stepping through the whole program with a debugger tells you where. I use assertions because they make my life easier. -- Rainer Deyke - rain...@eldwood.com
Re: Null references (oh no, not again!)
Walter Bright wrote: Rainer Deyke wrote: I use assertions because they make my life easier. Yeah, but you also said they were tedious g. Tedious, but better than the same code without the assertions. Non-nullable types would remove the need for manual assertions. -- Rainer Deyke - rain...@eldwood.com
Re: Null references (oh no, not again!)
Georg Wrede wrote: Rainer Deyke wrote: A sample size of one doesn't mean much. In my experience, none of those four factors account for a significant amount of bugs, since all of them (except integer overflow) can be caught without too much effort through the copious use of assertions. Well, you got it backwards. Sample size one is your experience. Sample size N is Unreal huge source, which has been written by N guys. It's one anecdote versus another anecdote. Neither has a significant sample size. (And you're not examining the whole body of work created by those N programmers. You're examining one project.) -- Rainer Deyke - rain...@eldwood.com
Re: Null references (oh no, not again!)
Ary Borenszweig wrote: I think nullable types can't be optional. How do you implement a linked list without them? You use a dummy value for the no next node? Naaah... A nullable type is, conceptually, a container that can contain either zero or one element. If there was no language or library support for nullable types, you could always use a dynamic array or some othet container instead. -- Rainer Deyke - rain...@eldwood.com
Re: new D2.0 + C++ language
Christopher Wright wrote: Games have strict performance requirements that a stop-the-world type of garbage collector violates. Specifically, a full collection would cause an undue delay of hundreds of milliseconds on occasion. If this happens once every ten seconds, your game has performance problems. This is not true of pretty much any other type of application. If you spend hundreds of milliseconds on garbage collection every ten second, you spend multiple percent of your total execution time on garbage collection. I wouldn't consider that acceptable anywhere. -- Rainer Deyke - rain...@eldwood.com
Re: new D2.0 + C++ language
Christopher Wright wrote: I was pulling numbers out of my ass. That's what I assumed. I'm a game developer. I use GC. 0.1 seconds out of every ten is a small amount to pay for the benefits of garbage collection in most situations. GC is useless for resource management. RAII solves the resource management problem, in C++ and D2. GC is a performance optimization on top of that. If the GC isn't faster than simple reference counting, then it serves no purpose, because you could use RAII with reference counting for the same effect. (No, I don't consider circular references a problem worth discussing.) -- Rainer Deyke - rain...@eldwood.com
Re: new D2.0 + C++ language
Sergey Gromov wrote: I think this is an overstatement. It's only abstract write buffers where GC really doesn't work, like std.stream.BufferedFile. In any other resource management case I can think of GC works fine. OpenGL objects (textures/shader programs/display lists). SDL surfaces. Hardware sound buffers. Mutex locks. File handles. Any object with a non-trivial destructor. Any object that contains or manages one of the above. Many of the above need to be released in a timely manner. For example, it is a serious error to free a SDL surface after closing the SDL video subsystem, and closing the SDL video subsystem is the only way to close the application window under SDL. Non-deterministic garbage collection cannot work. Others don't strictly need to be released immediately after use, but should still be released as soon as reasonably possible to prevent resource hogging. The GC triggers when the program is low on system memory, not when the program is low on texture memory. By my estimate, in my current project (rewritten in C++ after abandoning D due to its poor resource management), about half of the classes manage resources (directly or indirectly) that need to be released in a timely manner. The other 50% does not need RAII, but also wouldn't benefit from GC in any area other than performance. -- Rainer Deyke - rain...@eldwood.com
Re: Benchmark of try/catch
Daniel Keep wrote: int? value1 = atoi(0); // 0 int? value2 = atoi(#!$); // null Not quite; that assumes null isn't a valid value. What if the function was getInstance? T?? value = getInstance(); Here's how I understand it to work. Functions may return a Maybe!(T), which is either a T or None (note that None is not null), and is implicitly castable to T. 'T??' (aka 'Maybe!(Maybe!(T))') must be a valid type. In Haskell, a 'Maybe Maybe Integer' can be 'Nothing', 'Just Nothing', or 'Just Just 5'. In C++, given 'boost::optionalboost::optionalint o', you can have '!o', '!*o', or '**o == 5'. -- Rainer Deyke - rain...@eldwood.com
Re: Four things
bearophile wrote: C# has arrays of arrays as D, plus it has built-in multidimensional arrays too: http://msdn.microsoft.com/en-us/library/aa288453(VS.71).aspx They have some advantages: - On them the compiler can use some extra optimizations, common in all Fortran compilers. D seems a language fit for matrix processing, etc. So this may be useful. - Such matrixes can be reshaped on the fly, they just keep their line length as an extra parameter. - Being made of a single block of memory, the memory allocator wastes less memory, sometimes much less. You can try this yourself with a single stc.gc.malloc compared to the newing of a 2D matrix made of arrays of arrays. I am ignorant about this topic, but maybe Lucarella may say something. I use multi-dimensional arrays all the time. Arrays of arrays are totally inadequate as a substitute. However, I'm not sure what advantage a built-in type would provide over a library type. The GC used by dotnet C# is quite more efficient than the GC used by C# mono. This page is about the development of a future better mono GC: http://www.mono-project.com/Compacting_GC It also reminds me that a (single) good GC may manage two kinds of objects: safe objects, that may be moved to allow heap compaction, and pinned objects, coming from or referenced by unsafe modules. If most modules in a D project are going to be safe, then most objects may be unpinned, allowing the GC to move them. This in theory may lead to a D GC that is often as efficient as ones like HotSpot ones. Better GC is always nice, but I'd rather see the language stabilize before worrying about such implementation details. It says: Data types and behaviors of objects are described by classes and traits. Class abstractions are extended by subclassing and by a flexible mixin-based composition mechanism to avoid the problems of multiple inheritance. So it seems they have partially refused how classes are managed in D. I'd like to know if this is actually (a bit) better than the way OOP is done in D. If such things are better, D2/D3 may eventually copy something. Disclaimer: I haven't looked at Scala. If mixins are such a good replacement for multiple inheritance, then they are also a good replacement for single inheritance. Having single inheritance without multiple inheritance is /wrong/. Based on the above paragraph, it sounds like this criticism applies as much to Scala as it does to D and Java. -- Rainer Deyke - rain...@eldwood.com
Re: why Unix?
Christopher Wright wrote: If you need to use the command line, you really should use Unix, if it's reasonable. The Windows filesystem structure is a source of pain, and it's hard to work around. It's hard to get information about interacting with the OS from the command line. Windows and Cygwin just isn't that great a fit. I have both Linux and OS X readily available, but I still end up using the Windows command line (without cygwin) most of the time. I won't deny that the Unix-like systems have some nice utilities, but in my day-to-day activities, cmd.exe, Python, and my collection of custom utilities are all I need. -- Rainer Deyke - rain...@eldwood.com
Re: AAs
bearophile wrote: Immutable associative arrays may even define a toHash (computed only once, the first time it's needed), so they can be used as keys for other AAs/sets too. How would this work? Hash value calculated on conversion to immutable? Messy special case, plus the hash value may never be needed. Hash value calculated on first access and stored in the AA? Can't do, AA is immutable. Hash value calculated on first access and stored in a global table? The global table would prevent the AA from being garbage collected. I would like to see this happen, but I don't think D allows it. -- Rainer Deyke - rain...@eldwood.com
Re: AAs
bearophile wrote: You are right, there's a problem here, even once you have added an .idup to AAs. A hash value isn't data, it's metadata, so you may have a lazily computed mutable metadata of immutable data. Once computed the hash value essentially becomes an immutable. This sounds like the difference between logical const and physical const. I use the logical const features of C++ (along with the 'mutable' keyword) in C++ all the time for just this purpose. For better or worse, D has gone the physical const route. -- Rainer Deyke - rain...@eldwood.com
Re: Is typedef an alien?
Aenigmatic wrote: Is typedef (in D) a C/C++ legacy or is the dear orphan now adopted as a first-class citizen in the US of D? typedef in D is a new feature not found in C or C++. The typedef from C/C++ has been renamed to alias and extended to non-types in D. -- Rainer Deyke - rain...@eldwood.com
Re: override(T)
Lionello Lunesu wrote: Be careful with that reasoning. What about attributes? Properties? What about C# var vs. D auto? C# using vs. D import? In fact, what about the standard library? I wouldn't wind at all if D copied more from C#, and I don't even like C#. A natively compiled C# with extra features would be much more useful (and much easier to sell) than yet another incompatible object-oriented C variant. -- Rainer Deyke - rain...@eldwood.com
Re: Pure dynamic casts?
language_fan wrote: The cost of e.g. doubling computing power depends on the domain. If you are building desktop end user applications, they usually should scale from single core atoms to 8-core high-end enthusiastic game computers. So the cpu requirements shouldn't usually be too large. Usually even most of the 1-3 previous generations' hardware runs them just nicely. Now doubling the cpu power of a low-end current generation PC does not cost $1000, but maybe $20-50. You also need to consider how widely distributed your application is. If you force a million desktop PC users to upgrade their CPUs, you just wasted twenty to fifty million dollars of your customers' money (or, more likely, lost a million customers). -- Rainer Deyke - rain...@eldwood.com
Re: Is typedef an alien?
Chad J wrote: typedef uint ColorFormat; enum : ColorFormat { RGB, RGBA, HSV, CMYK, // etc } I always do the opposite in C++: namespace color_formats { enum ColorFormat { RGB, RGBA, HSV, CMYK, // ... }; } using color_formats::ColorFormat; This way I don't pollute my outer namespaces with enum symbols. -- Rainer Deyke - rain...@eldwood.com
Re: Pure dynamic casts?
language_fan wrote: I do not believe the market works this way. According to that logic popularity correlates with expenses. So if you have e.g. 1 billion users, even $1 in per-user hardware costs causes a billion dollar losses to customers. Is that unacceptable? On the other hand a program with a userbase of 10 might require a $100 hw upgrade and it is still ok? If you manage to sell a billion copies of your program, you can certainly afford to spend some extra time on optimizations and reach maybe another million users (which would be only 0.1% of your total user base, but a huge amount of money in absolute sales). If you're serving a vertical market of only 10, then your program is probably so expensive that another $1000 for hardware upgrades is peanuts. So yes, that's the way it works. A used 2.5 GHz Athlon XP with 1GB of RAM and 100GB of disk costs about $100. Anything below that is obsolete these days. Good luck selling anything to people who use older computers, they are probably broke anyways. Otherwise I just see it cheaper to build your apps slower and require hardware updates. Just imagine - a highly optimized $400 program is way too expensive for most users, a $50 program + $200 hw upgrade sounds just fine. If you just pull numbers of your ass, you can prove anything. Software is priced to optimize total income, which is net income per unit times number of units sold. Production costs are not factored in at all. So the real question is if your $50 software package sells enough additional units to make up for the increase in production costs. Competent programmers write reasonably efficient code from the start, and can take maybe another 10% of the development time to optimize the code to the point of diminishing returns. If you have competent programmers, your 8x price increase almost an order of magnitude off. Incompetent programmers don't just write slow code, they write buggy, non-reusable code. If you're using incompetent programmers, you're probably wasting more money on management than it would have cost to hire competent programmers in the first place. -- Rainer Deyke - rain...@eldwood.com
Re: The Non-Virtual Interface idiom in D
Andrei Alexandrescu wrote: interface Cloneable(T) if (is(T == class)) { private T doClone(); // must implement but can't call T clone()// this is what everybody can call { auto result = doClone(); assert(typeof(result) == typeof(this)); assert(this.equals(result)); return result; } } This sounds like a case for contract inheritance rather than two layers of functions. interface ComparableForEquality(T) { protected bool doEquals(T); final bool equals(T rhs) { auto result = doEquals(rhs); assert(rhs.equals(cast(T) this) == result); return result; } } This, on the other hand, does require two layers of functions if you want to remove the infinite recursion by replacing the 'equals' in the assertion with 'doEquals'. -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Walter Bright wrote: void bar(bool foo) { int a = void; if (foo) { a = 1; } else { a = 2; // Reuse variable. } int c = 3; } You now only have two variables, but both of them coexist at the end of the function. Unless the compiler applies a clever optimization, the compiler is now forced to allocate space for two variables on the stack. Not necessarily. The optimizer uses a technique called live range analysis to determine if two variables have non-overlapping ranges. It uses this for register assignment, but it could just as well be used for minimizing stack usage. That's the optimization I was referring to. It works for ints, but not for RAII types. It also doesn't (necessarily) work if you reorder the function: void bar(bool foo) { int a = void; int c = 3; if (foo) { a = 1; } else { a = 2; // Reuse variable. } } Of course, a good optimizer can still reorder the declarations in this case, or even eliminate the whole function body (since it doesn't do anything). -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Jeremie Pelletier wrote: Walter Bright wrote: They are completely independent variables. One may get assigned to a register, and not the other. Ok, that's what I thought, so the good old C way of declaring variables at the top is not a bad thing yet :) Strange how you can look at the evidence and arrive at exactly the wrong conclusion. Declaring variables as close as possible to where they are used can reduce stack usage, and never increases it. -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Jesse Phillips wrote: The thing is that memory safety is the only safety with code. That is such bullshit. For example, this: class A { } class B { } A x = new B; No memory access violation (yet). Clearly incorrect. Detecting this at compile time is clearly a safety feature, and a good one. You could argue that assigned a 'B' to a variable that is declared to hold an 'A' is already a memory safety violation. If so, then the exact argument also applies to assigning 'null' to the same variable. -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Jesse Phillips wrote: Yeah, it was brought to my attention that type safety by a friend could be another form. bearophile also brings up a good example. snip I think that is what Walter is getting at, you're not dealing with memory that is correct, when this happens the program should halt and be dealt with from outside the program. Type errors and null pointer errors both belong to the same class of errors, namely variables containing bogus contents. Some languages like Python detect both at runtime. That's fine for those languages. However, I prefer to detect as many errors as possible at compile time, especially for larger projects. Nullable types turn compile time errors into runtime errors which may or may not be detected during testing. In the worst case, nullable types lead to silent data corruption. Consider what happens when a bogus null field is serialized. -- Rainer Deyke - rain...@eldwood.com
Re: Interesting GCC extensions
Adam D. Ruppe wrote: Trying it, I see it doesn't actually work in initialization, but you can do it after the fact easily enough: int[101] a; a[0..9] = 1; a[10..99] = 2; a[100] = 3; This leaves elements 9 and 99 uninitialized. I assume the gcc version does not. -- Rainer Deyke - rain...@eldwood.com
Half-open versus closed ranges, was Re: Interesting GCC extensions
Adam D. Ruppe wrote: I think this makes D's syntax superior. We can say a[0..$] where gcc would have to use the more annoying a[0...$-1]. (pretending the $ worked of course) Although I generally prefer half-open ranges over closed ranges, both have their advantages and disadvantages. Advantages of half-open ranges: - Empty ranges are valid: [0 .. 0] - Easy for subranges to go to the end of the original: [x .. $] - Easy to split ranges: [0 .. x] and [x .. $] Advantages of closed ranges: - Symmetry. - Arguably easier to read. - ['a' ... 'z'] does not require awkward '+ 1' after 'z'. - [0 ... uint.max] is possible. -- Rainer Deyke - rain...@eldwood.com
Re: Rich Hickey's slides from jvm lang summit - worth a read?
Chris Nicholson-Sauls wrote: My personal experience with Scala, at least, has been that it doesn't hurt anything. Scala uses the Java garbage collector, which can take all kinds of abuse without flinching. -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Jeremie Pelletier wrote: struct NonNull(C) if(is(C == class)) { C ref; invariant() { assert(ref !is null); } T opDot() { return ref; } } This only catches null errors at runtime. The whole point of a non-null type is to catch null errors at compile time. -- Rainer Deyke - rain...@eldwood.com
Re: Null references redux
Jeremie Pelletier wrote: Rainer Deyke wrote: This only catches null errors at runtime. The whole point of a non-null type is to catch null errors at compile time. Thats what flow analysis is for, since these are mostly uninitialized variables rather than null ones. Nitpick: there are no uninitialized variables in D (unless you especially request them). There are explicitly initialized variables and default-initialized variables. I can see the argument for disabling default initialization and requiring explicit initialization. You don't even need flow analysis for that. However, that doesn't address the problem that non-null references are intended to solve. It's still possible to explicitly store a null values in non-null references without the problem being detected at compile time. -- Rainer Deyke - rain...@eldwood.com
Re: Multiple subtyping with alias this and nested classes
Max Samukha wrote: The above sucks because we can't specify which nameCollision gets implemented by which mixin. In current D, nameCollision of both interfaces is implemented by Flipper. I don't think that being able to define multiple separate functions with the same name and the same signature is a necessary feature of multiple inheritance, or even a particularly well thought-out one. Python is an example of a language without this feature, and multiple inheritance is very common in Python. -- Rainer Deyke - rain...@eldwood.com
Re: What does Coverity/clang static analysis actually do?
There is a simple and elegant solution to the problem of false positives in static analysis. Simple provide a way for the programmer to say, I know this looks dangerous, but I know what I'm doing, so trust me on this. This could be done by adding some new syntax, either a new keyword or a reused old keyword, or by changing the semantics of void initialization. For example, this would be an error: T t; if (i 10) { t = new T; } if (i 10) { t.f(); } This would be legal: T t = unchecked; if (i 10) { t = new T; } if (i 10) { t.f(); } But this would be an error: T t = unchecked; t.f(); As would this: T t = unchecked; f(t); 'unchecked' has the following properties: - It is an error to use the value of a variable that is initialized as 'unchecked'. - 'unchecked' tells the static analysis that the programmer knows what he's doing. The static analysis should therefore be liberal rather than conservative. - At runtime, 'unchecked' is equivalent to 'null' for non-nullable reference types. This increases the chance that the error will be detected at runtime. On the other hand, variables without any explicit initializer at all will have the following properties: - It is an error to use the value of the variable. - The static analysis is conservative by default. If it thinks the variable could be used uninitialized, an error is reported. - Since the static analysis guarantees that the variable will not be used uninitialized, there is no need to initialize the variable at runtime. -- Rainer Deyke - rain...@eldwood.com
Re: null references redux + Looney Tunes
bearophile wrote: Scala has a powerful type system that allows to implement such things in a good enough way: http://www.michaelnygard.com/blog/2009/05/units_of_measure_in_scala.html Either I'm missing something, or this system only checks units at runtime (which would make it both slow and unsafe). Boost.Units (C++) checks units at compile time. There is no reason why D could not use the same approach. -- Rainer Deyke - rain...@eldwood.com
Re: null references redux + Looney Tunes
Nick Sabalausky wrote: I've been thinking it might be nice to have both. Compile-time for obvious reasons but then also run-time ones that could do conversions: auto velocity = convert(meter/second)(distance / time); // Actual runtime-conversion I'm pretty sure Boost.Units already does this, although in general it's probably better to stick with SI units in your code and only perform conversions on input/output. -- Rainer Deyke - rain...@eldwood.com
unit libraries, was Re: null references redux + Looney Tunes
language_fan wrote: The only problem with these was that there was no way to signal the location of the type error in the client code, it always reported the location of the (static) assert in the library, which is pretty much useless. That's a compiler problem, no? I don't think this is a big deal either way. Unit errors should be exceedingly rare. The purpose of a unit library not to track down unit errors, but to formally prove that correct code is correct. -- Rainer Deyke - rain...@eldwood.com
Re: Use of first person in a book
Andrei Alexandrescu wrote: So I thought I'd ask a candid question in here. How do you feel about moderate use of the first person in a technical book? Do you find it comfortable, neutral, or cringeworthy? Occasionally a technical author finds that a personal anecdote or opinion is useful for illustrating a concept. The author then has these choices: 1. Leave it out. 2. Treat it as a universal fact. 3. Anonymize it. 4. Talk about himself in the third person. 5. Use first person. The first three options are clearly bad because information is lost. I prefer option 5 over option 4, but both are acceptable to me. -- Rainer Deyke - rain...@eldwood.com
Re: Rationals Lib?
dsimcha wrote: I guess I could have implemented some of these suggestions, but the idea was for this lib to be very simple (it's only about 300 lines of code so far) and agnostic to the implementation of the integers it's working on top of, with the caveat that, if you use something that's not arbitrary precision, the onus is on you to make sure nothing overflows. If anyone, for example, made a wrapper to the GNU multiprecision lib that looked like a D struct w/ operator overloading, it would be able to plug right into this library. If std.bigint improves, this library will automatically benefit. FWIW I use boost::rational quite a bit, and I've never felt the need to use bigints. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: Eiffel offers the old keyword that refers to the old object in a postcondition. But it seems quite wasteful to clone the object just to have a contract look at a little portion of the old object. You don't need to clone the whole object. You just need to cache the properties that are used with 'old'. In other words, this functionality: void push(T value); in { auto oldLength = length(); } out { assert(value == top()); assert(length == oldLength + 1); } ...can be expressed with this syntax: void push(T value); out { assert(value == top()); assert(length == old length + 1); } The syntax with 'old' is more concise, easier to read, and does the same thing. What's wrong with it (other than adding yet another keyword to the language)? -- Rainer Deyke - rain...@eldwood.com
Re: T[new] misgivings
Andrei Alexandrescu wrote: int[new] a; a = [1, 2, 3]; What should that do? This question can be rephrased as, should 'int[new]' be a reference type or a value type (or something else)? If 'int[new]' is a reference type, then it must rebind, because that's what assignment does for reference types. If 'int[new]' is a value type, then it must modify the array in place, because that's all it can do. If 'int[new]' is neither a reference type nor a value type, then we're back to (some of) the problems with slices. To answer the rephrased question: 'int[new]' should be a value type. W: Nobody complained about it with slices. FWIW, I found arrays in D1 so completely broken that I didn't it worth the effort to complain about every little detail. Everything about them was wrong. I consider them a textbook example of what not to do. -- Rainer Deyke - rain...@eldwood.com
Re: T[new] misgivings
Andrei Alexandrescu wrote: Rainer Deyke wrote: To answer the rephrased question: 'int[new]' should be a value type. Well Walter and I agreed they should be pass-by-reference. That doesn't mean they must be references, and the fact that the simplest syntax has the worst efficiency reminds me of iostreams. Given that D already has both value types and reference types, the addition of types that are passed by reference but otherwise act as value types actually seems reasonable. It make the language more orthogonal. Classes have one set of attributes. Structs have another. If the language absolutely needs to support both sets of attributes, I should at least be able to mix and match between them. So, what's the syntax for user-defined value types that are passed by reference going to be? ref struct? opPass? FWIW, I found arrays in D1 so completely broken that I didn't it worth the effort to complain about every little detail. Everything about them was wrong. I consider them a textbook example of what not to do. My perception is that you're in a minority. Anyway, if there's something that T[new] can help with, let us know. I was under the impression that arrays were generally considered broken, which is why 'T[new]' is now being introduced. -- Rainer Deyke - rain...@eldwood.com
Re: T[new] misgivings
Andrei Alexandrescu wrote: Then your argument building on similarity between the two is weakened. T[new] a; T[] b; a = [1, 2, 3]; b = [1, 2, 3]; Central to your argument was that the two must do the same thing. Since now literals are in a whole new league (they aren't slices because slices can't be assigned to arrays), the cornerstone of your argument goes away. Actually [1, 2, 3] looks more like an array than a slice to me. Arrays can be assigned to slices, no? -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: class A { int fun() { ... } int gun(int) { ... } int foo() in { } out(result) { if (old.fun()) assert(old.gun(5)); else assert(old.fun() + old.gun(6)); foreach (i; 1 .. old.fun()) assert(gun(i * i)); } ... } Now please tell what's cached and in what order. The following are cached, in this order: fun() gun(5) gun(6) Old values are calculated in the order in which they appear in the function, but only once each. However, I strongly prefer the following syntax: class A { int fun() { ... } int gun(int) { ... } int foo() in { } out(result) { if (old(fun())) assert(old(gun(5))); else assert(old(fun()) + old(gun(6))); foreach (i; 1 .. old(fun())) assert(gun(i * i)); } ... } This lets you distinguish between the following cases: old(f().g()) old(f()).g() It also lets you cache non-members: old(arg); old(global_var); For example: void increment_global_counter() out { global_counter = old(global_counter) + 1; } -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: Rats, I meant assert(old.gun(i * i)). That's what compounds the difficulty of the example. That wouldn't be allowed. More specifically 'old(gun(i * i))' wouldn't be allowed. 'old(this).gun(i * i)' would be allowed, but probably wouldn't do what you want it to do. 'old(this.clone()).gun(i * i)' would be allowed and would work, assuming that the 'clone' method is defined and has the right semantics. The general rule is that for any 'old(expr)', 'expr' only has access to variables that are accessible in the 'in' block. Preferably const access. I honestly believe the whole old thing can't be made to work. Shall we move on to other possibilities instead of expending every effort on making this bear dance? It definitely /can/ be made to work, for some value of work. It sacrifices the natural order of evaluation to gain a concise and intuitive syntax. I don't think it should be dismissed out of hand. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Rainer Deyke wrote: Andrei Alexandrescu wrote: I honestly believe the whole old thing can't be made to work. Shall we move on to other possibilities instead of expending every effort on making this bear dance? It definitely /can/ be made to work, for some value of work. It sacrifices the natural order of evaluation to gain a concise and intuitive syntax. I don't think it should be dismissed out of hand. Also, from the Eiffel docs (http://archive.eiffel.com/doc/online/eiffel50/intro/language/invitation-07.html): The notation 'old expression' is only valid in a routine postcondition. It denotes the value the expression had on routine entry. It seems that Eiffel had 'old' semantics that I've proposed all along. Any significant problems with this approach would have been discovered by the Eiffel community by now. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: Rainer Deyke wrote: Also, from the Eiffel docs (http://archive.eiffel.com/doc/online/eiffel50/intro/language/invitation-07.html): The notation 'old expression' is only valid in a routine postcondition. It denotes the value the expression had on routine entry. It seems that Eiffel had 'old' semantics that I've proposed all along. Great. Others brought it up too, inspired from Eiffel. Any significant problems with this approach would have been discovered by the Eiffel community by now. There is no problem if a copy of the object is made upon entry in the procedure. That's what I think Eiffel does. I was hoping to avoid that by allowing the out contract to see the definitions in the in contract. Copying the object would be completely broken, so I'm sure that that's *not* how Eiffel does it. It denotes the value the expression had on routine entry. In other words, the expression is evaluated once, on routine entry, and the result is cached for use in the postcondition. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Christopher Wright wrote: Rainer Deyke wrote: It seems that Eiffel had 'old' semantics that I've proposed all along. Any significant problems with this approach would have been discovered by the Eiffel community by now. It requires duplicating the object. If the object is mutable, this requires duplicating it and recursively duplicating everything it references. If the object is immutable, this is free. There is no the object. void f(string fname) out { file_size(fname) = old(file_size(fname)); } -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Leandro Lucarella wrote: Rainer Deyke, el 17 de octubre a las 14:24 me escribiste: There is no the object. There is an object if you have this: void f(SomeObjectWithLotsOfReferences obj) out { assert(old(obj).some_check()); } If 'obj' is a reference type and the reference itself wasn't modified, then 'old(obj)' is the same as 'obj'. Objects are only copied if you explicitly copy them. 'old(x)' means the cached value of evaluating 'x' at the beginning of the routine. No more, no less. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: Rainer Deyke wrote: Copying the object would be completely broken, so I'm sure that that's *not* how Eiffel does it. It denotes the value the expression had on routine entry. In other words, the expression is evaluated once, on routine entry, and the result is cached for use in the postcondition. What if the expression is conditioned by the new state of the object? Not allowed. Since 'old(x)' is the value of 'x' evaluated at the beginning of the function, it must be possible to evaluate 'x' at the beginning of the function. Either rewrite the assertion or drop it. I have a feeling that this will only rarely be an issue. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: If x is a complex expression and part of a complex control flow, it becomes highly difficult what it means at the beginning of the function. It also becomes difficult to find a way to distinguish good cases from bad cases without being overly conservative. It looks like a more or less straightforward AST transformation to me. in { } body { F(); } out { G(old(x)); } = { auto old_x = x; try { F(); } finally { G(old_x); } } Repeat for each instance of 'old', in order of appearance. OK, it's not entirely trivial, but it's not prohibitively difficult either. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Rainer Deyke wrote: { auto old_x = x; try { F(); } finally { G(old_x); } } Not 'finally', unless postconditions are checked when the function terminates with an exception. This is closer to correct: { auto old_x = x; // Preconditions go here. F(); // -- function body G(old_x); // -- postcondition } -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: It is if x is an _arbitrarily complex_ expression, and if that expression is part of a _complex control flow_. The language definition would have to decide exactly where complex is too complex in the expression or the control flow. That complicates the language. Also, by necessity the feature will be limited and will not give people enough freedom. It's lose-lose. The complexity of the expression is irrelevant, since the expression is simply moved to the beginning of the function as a unit. Complex expressions are just simple expressions applied recursively. The compiler already knows how to deal with complex expressions. Control flow is also irrelevant because it is simply ignored. The AST transformation sees this: blah blah old(x) blah old(y) blah ..and turns it into this: blah blah cached_value_0 blah cached_value_1 blah Yes, this means that the expressions 'x' and 'y' cannot use any local variables from the postcondition block. If you want to cache values in the precondition block for use in the postcondition block, you can't wait until you reach the postcondition block before deciding what to cache. This limitation exists whether the caching is manual or automated. If you insist on arguing about this, please direct your complaints to the Eiffel community and tell *them* how the language feature they've been using for years is broken. If there really is something wrong with Eiffel-style 'old' expressions, I'm sure the Eiffel community would know about it. -- Rainer Deyke - rain...@eldwood.com
Re: Communicating between in and out contracts
Andrei Alexandrescu wrote: Rainer Deyke wrote: The expression may mutate stuff. It shouldn't. It's an error if it does, just like it's an error for an assertion or post/precondition to have any side effects. It would be nice if the compiler could catch this error, but failing that, 'old' expressions are still no worse than assertions in this respect. If you know about Eiffel's old, I'd appreciate if you explained how it works. I am arguing exactly because I don't know. My understanding is that neither of us currently knows how it works, so I don't think it's ok to refer me to Eiffel. My suggestion to ask the Eiffel community directly was meant seriously. -- Rainer Deyke - rain...@eldwood.com
Re: Eliminate new for class object creation?
Andrei Alexandrescu wrote: I hereby suggest we get rid of new for class object creation. What do you guys think? *applause* 'X(x)' and 'new X(x)' have distinct meanings in C++. In Java/C#/D, the 'new' is just line noise. -- Rainer Deyke - rain...@eldwood.com
Re: LRU cache for ~=
Andrei Alexandrescu wrote: One surprising (but safe) behavior that remains with slices is this: void fun(int[] a) { a[0] = 0; a ~= 42; a[0] = 42; } The caller may or may not see 42 in the first slot after the call. Your definition of safe is clearly not aligned with mine. -- Rainer Deyke - rain...@eldwood.com
Re: LRU cache for ~=
Denis Koroskin wrote: On Tue, 20 Oct 2009 03:00:57 +0400, Rainer Deyke rain...@eldwood.com wrote: Andrei Alexandrescu wrote: One surprising (but safe) behavior that remains with slices is this: void fun(int[] a) { a[0] = 0; a ~= 42; a[0] = 42; } The caller may or may not see 42 in the first slot after the call. Your definition of safe is clearly not aligned with mine. Safe as in SafeD (i.e. no memory corruption) :) If the caller wasn't expecting the array to be modified, then that's a textbook case of memory corruption. If the caller /was/ expecting the array to be modified, then it's the opposite, which isn't much better. -- Rainer Deyke - rain...@eldwood.com
Re: LRU cache for ~=
Andrei Alexandrescu wrote: Rainer Deyke wrote: Your definition of safe is clearly not aligned with mine. What's yours? Safety is not an absolute, but a question of degree. The harder it is to write incorrect code, the safer the language. One key element of this is deterministic behavior. If you rely on the whim of the runtime to determine if two slices refer to the same data, then it becomes much harder to reason about or test the code. -- Rainer Deyke - rain...@eldwood.com
Re: Eliminate new for class object creation?
Bill Baxter wrote: On Mon, Oct 19, 2009 at 4:00 PM, Rainer Deyke rain...@eldwood.com wrote: 'X(x)' and 'new X(x)' have distinct meanings in C++. In Java/C#/D, the 'new' is just line noise. Well, I think new Foo is how you create a struct on the heap in D. So it's not exactly line noise. I should have specified, for classes. -- Rainer Deyke - rain...@eldwood.com
Re: LRU cache for ~=
Andrei Alexandrescu wrote: Rainer Deyke wrote: If the caller wasn't expecting the array to be modified, then that's a textbook case of memory corruption. [citation needed] I guess we need to define memory corruption first. Memory corruption is when a piece of code erroneously overwrites memory. That applies here. Do you have a better definition? -- Rainer Deyke - rain...@eldwood.com
Re: LRU cache for ~=
Andrei Alexandrescu wrote: Rainer Deyke wrote: I guess we need to define memory corruption first. Memory corruption is when a piece of code erroneously overwrites memory. Where's that quote from? It's my own attempt to define memory corruption. It's equivalent to the definition in Wikipedia: Memory corruption happens when the contents of a memory location are unintentionally modified due to programming errors. [...] Do you have a better definition? I do, Then post it. but since you mentioned a textbook case of memory corruption, I was curious which textbook that wold come from. My use of the word textbook was idiomatic, not literal. From http://www.answers.com/topic/textbook: text·book (tĕkst'bʊk') [...] adj. Being a characteristic example of its kind; classic: a textbook case of schizophrenia. -- Rainer Deyke - rain...@eldwood.com
Re: Eliminate new for class object creation?
Andrei Alexandrescu wrote: Lionello Lunesu wrote: Also, somebody mentioned using 'new' to allocate structs on the heap; I've never actually done that, but it sounds like using 'new' would be the perfect way to do just that. Yah, I guess I'll drop it. Consistency with structs demands that for a class type 'X', 'new X' allocates a *reference*, not an instance, on the heap. -- Rainer Deyke - rain...@eldwood.com
Re: this() not executing code on structs
Andrei Alexandrescu wrote: Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult. I wonder how much of a problem that could be in practice. I realized today that the Counted example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D. In C++ the counted example looks like this: struct Counted { static unsigned count; unsigned myCount; Counted() { myCount = count++; } Counted(const Counted rhs) { myCount = count++; } Counted operator=(const Counted rhs) { // no writing to myCount return *this; } ~Counted() { --count; } } In D there's no chance to write Counted because you can always create Counted objects without executing any code. struct Counted { static uint count; uint myCount; this() { myCount = count++; } // ERROR this(this) { myCount = count++; } ref Counted opAssign(Counted rhs) { // no writing to myCount return this; } ~this() { --count; } } This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D. Any struct that uses dynamic memory allocation internally. Any struct that registers its instances in some dort of global registry. 'ValueType!T', which turns reference type 'T' into a value type. A 'ScopedLock' variant that uses a single global mutex. RAII wrappers over global initialization/deinitialization functions. A 'UniqueId' struct that initializes to a value that is guaranteed to be distinct from the vale of any other 'UniqueId' used by the program. -- Rainer Deyke - rain...@eldwood.com
Re: Semicolons: mostly unnecessary?
Christopher Wright wrote: This only leaves the possibility of removing semicolons entirely. This would mandate newlines as separators instead. Then you'd need a different character to negate newlines. The C/C++ crowd would leave at this point, since that's just silly. (Though less typing.) Trivial solution: use indentation. If a line is more deeply indented than the previous line, it's a continuation of the previous statement. (Of course the people who complain about significant newlines are only going to scream louder about significant indentation, but you can't please everybody.) -- Rainer Deyke - rain...@eldwood.com
Re: Array, AA Implementations
Andrei Alexandrescu wrote: IMHO any container must offer at the very least (I'll use stylized signatures): 2. Iterate using at least a one-pass range OnePassRange!E opSlice(); Requiring this to be O(1) seems silly, since the actual iteration will never be O(1) and can't even be guaranteed to be O(n). 3. Remove some element from the container and give it to me E removeAny(); IIRC, C++ had a good reason (related to exception safety) for separating retrieval and removal operations. Note that even though length() is not part of the interface (as is in Gobo's library), it can be computed through iteration with a generic algorithm. The idea is to not force containers to write that themselves or ungainly cache the length. If the length is often needed, caching is much more efficient than recalculating from scratch. -- Rainer Deyke - rain...@eldwood.com
Re: Semicolons: mostly unnecessary?
Simen Kjaeraas wrote: On Thu, 22 Oct 2009 05:39:10 +0200, Rainer Deyke rain...@eldwood.com wrote: Trivial solution: use indentation. If a line is more deeply indented than the previous line, it's a continuation of the previous statement. All spaces, I presume? :p Tabs wouldn't be a problem so long as they are used consistently (one tab per indentation level). Given two whitespace sequences A and B, there are four possibilities: - They are identical, so they represent the same indentation. - A is a prefix of B, so B represents the deeper indentation. - B is a prefix of A, so A represents the deeper indentation. - Neither is a prefix of the other, which would be a syntax error. -- Rainer Deyke - rain...@eldwood.com
Re: Private enum members + Descent
Denis Koroskin wrote: On Sun, 25 Oct 2009 02:22:51 +0300, Michel Fortin michel.for...@michelf.com wrote: Sounds like you want enum inheritance: one enum being a superset of another. :-) I was about to post the same thing. It was previously proposed, but it was rejected because it works backwards: downcasting is allowed while upcasting is prohibited. Just like contravariant functions. Only less so. -- Rainer Deyke - rain...@eldwood.com
Re: Restricting ++ and --
bearophile wrote: Removing those operators from D, as Python, may look excessive. So a possible compromise can be: - Deprecate the pre versions: --x and ++x There is nothing wrong with the prefix versions. '--x' is the equivalent of 'x -= 1', but with less excessive typing. Any confusing expression using prefix ++/-- will be just as confusing if rewritten to use +=/-=. On the other hand, there are two things wrong with the post versions: - They use postfix notation, unlike all other unary operators in D. - They have confusion semantics. I don't use postfix ++/--. I wouldn't mind if they were removed entirely. However, the prefix versions should be kept, and allowed in compound expressions. -- Rainer Deyke - rain...@eldwood.com
Re: Permitted locations of a version condition
Phil Deets wrote: mixin(qENUM enum Tag { A, B, ENUM~(Version!(symbol)?qENUM C, D, ENUM:)~qENUM E, } ENUM); That's not pretty, but it's good enough for me; so I'll probably not do any compiler hacking. Not pretty is putting it very lightly. What's happening in the D community is the same thing that's already happened in the C++ community: - The language is missing some useful features. - The language is also missing an elegant powerful macro system that could be used to add those features. - However, the language has other features that can be abused to provide the functionality in a syntactically ugly way. - D programmers use these language features to write powerful but ugly code. - The language develops a reputation for being overly complex and difficult to read. What makes this case particularly bad is that Walter deliberate chose to limit the power of the 'version' construct in order to prevent overly complex read-only code. -- Rainer Deyke - rain...@eldwood.com
Re: module hijacking
hasenj wrote: This is also possible in python. Has this ever caused a real problem? or is it just a theoretical problem? In Python, this has caused problems for me. It's less likely to cause a problem in D because D makes better use of packages. Python just dumps everything into the root package; D has 'std'. -- Rainer Deyke - rain...@eldwood.com
Re: safety model in D
Andrei Alexandrescu wrote: Rainer Deyke wrote: '-safe' turns on runtime safety checks, which can be and should be mostly orthogonal to the module safety level. Runtime vs. compile-time is immaterial. The price of compile-time checks is that you are restricted to a subset of the language, which may or may not allow you to do what you need to do. The price of runtime checks is runtime performance. Safety is always good. To me, the question is never if I want safety, but if I can afford it. If I can't afford to pay the price of runtime checks, I may still want the compile-time checks. If I can't afford to pay the price of compile-time checks, I may still want the runtime checks. Thus, to me, the concepts of runtime and compile-time checks are orthogonal. A module either passes the compile-time checks or it does not. It makes no sense make the compile-time checks optional for some modules. If the module is written to pass the compile-time checks (i.e. uses the safe subset of the language), then the compile-time checks should always be performed for that module. -- Rainer Deyke - rain...@eldwood.com
Re: safety model in D
Andrei Alexandrescu wrote: First off: _all_ languages except C, C++, and assembler are or at least claim to be safe. All. I mean ALL. Did I mention all? If that was some ideology that is not realistic, is extremely difficult to achieve, and ends up too painful to use, then such theories would be difficult to corroborate with ALL. Walter and I are in agreement that safety is not difficult to achieve in D and that it would allow a great many good programs to be written. You're forgetting about all other system programming languages. Also, many of these claims to safety are demonstrably false. The text is very approachable and informative, and I suggest anyone interested to read through page 5 at least. I think it's a must for anyone participating in this to read the whole thing. Cardelli distinguishes between programs with trapped errors versus programs with untrapped errors. Yesterday Walter and I have had a long discussion, followed by an email communication between Cardelli and myself, which confirmed that these three notions are equivalent: a) memory safety (notion we used so far) b) no undefined behavior (C++ definition, suggested by Walter) c) no untrapped errors (suggested by Cardelli) They are clearly not equivalent. ++x + ++x has nothing to do with memory safety. Conversely, machine language has no concept of undefined behavior but is clearly not memory safe. Also, you haven't formally defined any of these concepts, so you're basically just hand-waving. -- Rainer Deyke - rain...@eldwood.com
Re: Safety, undefined behavior, @safe, @trusted
Andrei Alexandrescu wrote: Oh how cool. So it turns out that SafeD can be 100% implemented on a safe VM. This is also true of regular unsafe C. -- Rainer Deyke - rain...@eldwood.com
Re: safety model in D
Andrei Alexandrescu wrote: Rainer Deyke wrote: You're forgetting about all other system programming languages. Delphi. Also, many of these claims to safety are demonstrably false. Which? I can get Python to segfault. Memory safety is defined formally in Pierce's book. Do you mean Types and programming languages by Benjamin C. Pierce? According to Google books, it does not contain the phrase memory safety. It does contain a section of language safety, which says that a safe language is one that protects its own abstractions. By that definition, machine language is safe, because it has no abstractions to protect. Another quote: Language safety can be achieved by static checking, but also by run-time checks that trap nonsensical operations just at the moment when they are attempted and stop the program or raise an exception. In other words, Pierce sees runtime checks and compile-time checks as orthogonal methods for providing the same safety. Undefined behavior is defined by the C++ standard. Undefined behavior is a simple concept: the language specification does not define what will happen when the program invokes undefined behavior. Undefined behavior can be trivially eliminated from the language by replacing it with defined behavior. If a language construct is defined to trash the process memory space, then it is not undefined behavior. Cardelli defines trapped and untrapped errors. Untrapped error: An execution error that does not immediately result in a fault. I can't find his definition of execution error, which makes this definition useless to me. -- Rainer Deyke - rain...@eldwood.com
Re: D array expansion and non-deterministic re-allocation
Walter Bright wrote: It's deterministic in the sense that if you run the program again with the same inputs, you will get the same result. This is a highly useful attribute for testing and debugging. On the same platform, with the same compiler, compiler settings, and standard library implementation. That makes it harder to test, not easier. You now have to test with multiple compilers. It's safe as in memory safe. This is as opposed to undefined-behavior, which is not memory safe. A buffer overflow is an example of undefined-behavior. The current behavior is unsafe in that you can accidentally have two variables pointing at the same buffer. Let's say one buffer holds network input and the other holds some bytecode to execute. Boom - a bug that can be exploited to execute malicious (byte-)code. -- Rainer Deyke - rain...@eldwood.com
Re: Short list with things to finish for D2
Andrei Alexandrescu wrote: I am thinking that representing operators by their exact token representation is a principled approach because it allows for unambiguous mapping, testing with if and static if, and also allows saving source code by using only one string mixin. It would take more than just a statement that it's hackish to convince me it's hackish. I currently don't see the hackishness of the approach, and I consider it a vast improvement over the current state of affairs. Isn't opBinary just a reduced-functionality version of opUnknownMethod (or whatever that is/was going to be called)? T opBinary(string op)(T rhs) { static if (op == +) return data + rhs.data; else static if (op == -) return data - rhs.data; ... else static assert(0, Operator ~op~ not implemented); } T opUnknownMethod(string op)(T rhs) { static if (op == opAdd) return data + rhs.data; else static if (op == opSub) return data - rhs.data; ... else static assert(0, Method ~op~ not implemented); } I'd much rather have opUnknownMethod than opBinary. If if I have opUnknownMethod, then opBinary becomes redundant. -- Rainer Deyke - rain...@eldwood.com
Re: Conspiracy Theory #1
dsimcha wrote: == Quote from Denis Koroskin (2kor...@gmail.com)'s article It would be negligible. The idea is that unions of reference and non-reference types are such a corner case that they could be handled conservatively as a special case, and then it's possible, at least in principle, to deal with the other 99.9% of cases precisely and being conservative in 0.1% of cases is really of no practical significance. Yes, but a moving GC needs to be 100% precise, not 99.9%. -- Rainer Deyke - rain...@eldwood.com
Re: Conspiracy Theory #1
dsimcha wrote: == Quote from Rainer Deyke (rain...@eldwood.com)'s article Yes, but a moving GC needs to be 100% precise, not 99.9%. Not if you allow pinning, which we'd need anyhow for untyped, conservatively scanned memory blocks. If you allow pinning then you no longer get the full benefits of a moving gc. It would be nice to be able to trade untyped, conservatively scanned memory blocks for a better gc. -- Rainer Deyke - rain...@eldwood.com
Re: Pure, Nothrow in Generic Programming
Andrei Alexandrescu wrote: Denis Koroskin wrote: Points 2 and 3 introduce undefined behavior, which is not allowed in SafeD :p s/undefined/implementation-defined/ Behavior is only implementation-defined if the implementation actually defines it. When targeting a specific implementation of a language, there is no difference between implementation-defined behavior and just plain defined behavior. I think the term you are looking for is non-deterministic, which describes an operation which is defined to have one of several possible effects, but which of these effects actually takes place is not defined. Incidentally, it is sometimes possible to send output to a physical device by merely reading from a memory-mapped I/O address. Therefore the set of possible results of memcmp includes sending garbage output to a physical device. -- Rainer Deyke - rain...@eldwood.com
Re: Phobos packages a bit confusing
Pelle Månsson wrote: File looks like a constructor. You are not constructing a file you open for reading. open by itself is ambiguous. What are you opening? A window? A network port? I think the word file needs to be in there somewhere to disambiguate. -- Rainer Deyke - rain...@eldwood.com
Re: Phobos packages a bit confusing
Pelle Månsson wrote: Rainer Deyke wrote: open by itself is ambiguous. What are you opening? A window? A network port? I think the word file needs to be in there somewhere to disambiguate. Something like new BufferedReader(new FileReader(foo.txt))? It's quite unambiguous. No, like 'file(foo.txt)' or 'fopen(foo.txt)'. There's always a trade-off between verbosity and clarity, but names that are both reasonably short and reasonably unambiguous exist. -- Rainer Deyke - rain...@eldwood.com
Re: should postconditions be evaluated even if Exception is thrown?
Andrei Alexandrescu wrote: If a function throws a class inheriting Error but not Exception (i.e. an unrecoverable error), then the postcondition doesn't need to be satisfied. If an 'Error' is truly unrecoverable, then there's no point in treating it as an exception. Just dump the core and get out. The very existence of 'Error' suggests that recovery is possible, so the same rules should apply to 'Error' and 'Exception'. I just realized that postconditions, however, must be satisfied if the function throws an Exception-derived object. There is no more return value, but the function must leave everything in a consistent state. For example, a function reading text from a file may have the postcondition that it closes the file, even though it may throw a malformed file exception. Throwing an exception usually indicates a failure to reach the normal postcondition, but functions often have another postcondition that applies when the function terminates from an exception. Common postconditions in the case of an exception include: - The object is in a undetermined but valid state. (The basic exception guarantee in C++ lingo.) - The function has no side effects, i.e. all objects are in the same state as they were before the function was called. (The strong exception guarantee.) -- Rainer Deyke - rain...@eldwood.com
Re: switch case for constants-only?
Nick Sabalausky wrote: As I mentioned earlier, that should be semantically equivilent to: int x = 1, y = 1; if(z == x) { ... } else if(z == y) { ... } In fact, it's already semantically equivilent to that, except that x and y are currently required to be known at compile-time. I assume the same rule applies to 'goto case'? int i = 0, j = 0; switch (0) { case i: goto case j; // Oops, infinite loop. case j: // Never reached. } I'm basically in favor of this change - it increases the expressive power and uniformity of the language at little cost - but corner cases like this bother me. -- Rainer Deyke - rain...@eldwood.com