Re: The CAPI Manifesto
Walter Bright , dans le message (digitalmars.D:146786), a écrit : > If you want to add a layer on top of the C API, that would be fine. std.zlib > is > an example of that. > > But the idea of CAPI is NOT to add a layer. Not fix, extend, refactor, > improve, etc. I definitely agree with that, no attempt should be made to fix anything. However, if you want only direct translation, the only way I see is to treat all defines as mixins. That mean all macro will become very tedious to use. Even there, there are choices to make (how do you translate multi-parameter macros ?). So I think a minimum of adaptation should be provided. Here is an example of how this could be made: #define square(x) ((x)*(x)) => // direct translation mixin template!(string x)square { enum square = '((' ~ x ~ ')*(' ~ x ~ '))'; } // adapted version T square(T)(T x) { return x*x; } Which version should be added ? Both do not do the same thing ! As you know, In the first one, if x is a function call, it is called twice, and it the other one, it is called only once. So if I follow the rule: no fix, no extend, etc, I must include only the direct translation. But the CAPI library will not be as usable as the c one. Then I miss the purpose of CAPI: make d as usable as c when using c libraries. So which version should be added: I think it is both: // direct translation mixin template!(string x)M_square { enum M_square = '((' ~ x ~ ')*(' ~ x ~ '))'; } // adapted version T square(T)(T x) { return mixin M_square!q{x}; } So this is what I propose: Direct translation have to be provided. Adapted version can be provided alongside the direct translation of the d header. Adaptaded version must be a direct forward call to the direct version (note here that the adapted. Rules will have to be defined to avoid name clashes (for example, here, I used a the direct name for the usable version, and M_ prefix for the mixin version, but we could decide other rules). Macros are a big issue. And I think abvious translating, such as const char* to string, or pointer-length pairs to dynamic arrays is about the same problem. double array_sum(double* a, size_t length); => double array_sum(double* a, size_t length); // AND double array_sum(double[] a) { return array_sum(a.ptr, a.length); } That is very little work. The direct translation is mandatory, and the adapted translation is not. But when the translation is obvious, there is no reason for everyone to make it on it's corner. Make it in the public header and share it! I order to remain consistent, adaptation will have to obey to very precise rules, that have to be set. No fix, no extend, no refactor, no improve, etc. Just a forward call, to have, in addition to the C API, an API that use D's power: using enums, inline functions, for defines instead of the direct mixin translation. Using D's arrays instead of C arrays, etc. could be nice too. What translation should be provided ? What rules to translate defines ? Is translation of pointer+length pair to array worth doing ? What about stringz and strings ? Where to draw the line ? -- Christophe
Re: Why the hell doesn't foreach decode strings
Walter Bright , dans le message (digitalmars.D:147161), a écrit : > On 10/20/2011 9:06 PM, Jonathan M Davis wrote: >> It's this very problem that leads some people to argue that string should be >> its own type which holds an array of code units (which can be accessed when >> needed) rather than doing what we do now where we try and treat a string as >> both an array of chars and a range of dchars. The result is schizophrenic. > > Making such a string type would be terribly inefficient. It would make D > completely uncompetitive for processing strings. I definitely agree with you, but I have a piece of news for you : The whole phobos alreaday treats strings as a dchar ranges, and IS inefficient for processing strings. The fact is: char[] is not char[] in phobos. It is Range!dchar. This is aweful and schizophrenic, and also inefficient. The purpose was to allow people to use strings without knowing anything about unicode. That is why Jonathan proposed having a specific string structure to manipulate strings without having to worry about unicode, just like how strings are currently manipulated in phobos, and letting char[] be char[], and be as efficient as they should be. I was not there when it was decided to treat strings as dchar ranges, but now it is done. The only thing I can do is use ubyte[] instead of char[] so that phobos treat them as proper arrays, and propose optimized overloads for various phobos algorithm to make them as efficient as they should be (which I didn't find the time to do yet). -- Christophe
Re: Deterministic life-time storage type
Michel Fortin , dans le message (digitalmars.D:164824), a écrit : > So with your system, how do you write the swap function? I've thought about that. The scope(label) is the key. void T swap(T)(scope T a, scope(a) T b) { scope(a) tmp = a; a = b; b = tmp; } scope(inout) would also do the trick, since it is implicitely shared between parameters and return values. -- Christophe
Re: Constraints
"Ibrahim Gokhan YANIKLAR" , dans le message (digitalmars.D:166850), a écrit : I would even have: concept CInputRange(E) { alias E ElementType; static assert (isDefinable!typeof(this)); static assert (isRange!typeof(this)); @property bool empty(); @property void popFront(); @property ElementType front(); } Concepts allows to state your intent when you create a type. It self documents the code more nicely that a static assert. It would be much easier for newcomers to understand concepts. Finally, by creating a hierarchy, concepts allows to determine which is the more specialized template. Even if it is not a priority to add this to the langage, I don't think it is just syntactic sugar. Several problems should be solved though: concept CInfiniteRange(E) : CInputRange(E) { // empty was declared to be a property, here it is an enum. enum bool empty = false; // or: final bool empty() { return false; } } struct SimpleCounter(E) : CInfiniteRange(E) { // front was declared as a property, here it is a member variable E front = 0; void popFront() { ++front; } //or: E front_ = 0; @property E front() const { return front_; } // now front is const @property void popFront() { ++front_; } } It will not be obvious to define what is allowed and what is not so that the flexibility of current template constraints is reached, while keeping the definition natural. -- Christophe
Re: deprecating std.stream, std.cstream, std.socketstream
"Steven Schveighoffer" , dans le message (digitalmars.D:167548), a > My new design supports this. I have a function called readUntil: > > https://github.com/schveiguy/phobos/blob/new-io2/std/io.d#L832 > > Essentially, it reads into its buffer until the condition is satisfied. > Therefore, you are not double buffering. The return value is a slice of > the buffer. > > There is a way to opt-out of reading any data if you determine you cannot > do a full read. Just return 0 from the delegate. Maybe I already told this some time ago, but I am not very comfortable with this design. The process delegate has to maintain an internal state, if you want to avoid reading everything again. It will be difficult to implement those process delegates. Do you have an example of moderately complicated reading process to show us it is not too complicated? To avoid this issue, the design could be reversed: A method that would like to read a certain amount of character could take a delegate from the stream, which provides additionnal bytes of data. Example: // create a T by reading from stream. returns true if the T was // successfully created, and false otherwise. bool readFrom(const(ubyte)[] delegate(size_t consumed) stream, out T t); The stream delegate returns a buffer of data to read from when called with consumed==0. It must return additionnal data when called repeatedly. When it is called with a consumed != 0, the corresponding amount of consumed bytes can be discared from the buffer. This "stream" delegate (if should have a better name) should not be more difficult to implement than readUntil, but makes it more easy to use by the client. Did I miss some important information ? -- Christophe
Re: The more interesting question
"Steven Schveighoffer" , dans le message (digitalmars.D:167556), a > toStringz can allocate a new block in order to ensure 0 gets added. This > is ludicrous! > > You are trying to tell me that any time I want to call a C function with a > string literal, I have to first heap-allocate it, even though I *know* > it's safe. How about "mystring\0".ptr ?
Re: stream interfaces - with ranges
I don't have time to read the whole discussion right now, but I've thought since our exchange here about buffered stream. I've imagined something close to, but quite different from you buffered stream, where the length of the buffer chunk can be adapted, and the buffer be poped by an arbitrary amount of bytes: I reuse the name front, popFront and empty, but it may not be such a good idea. struct BufferedStream(T) { T[] buf; size_t cursor; size_t decoded; InputStream input; // returns a slice to the n next elements of the input stream. // this slice is valid until next call to front only. T[] front(size_t n) { if (n <= decoded - cursor) return buf[cursor..cursor+n]; if (n <= buffer.length) { ... // move data to the front of the buffer and read new data to // fill the buffer. return buf[0..n]; } if (n > buf.length) { ... // resize buffer and read new data to fill the buffer return buf[0..n]; } } // pop the next n elements from the buffer. void popFront(size_t n) { cursor += n; } void empty() { return input.eof && cursor == buf.length; } } This kind of buffered stream enable you read data by varying chunk size, but always read data by an amount that is convenient for the input stream. (and front could be made to return a buffer with the size that is most adequate for the stream when called with size_t.max as n). More importantly, it allows to peak at an arbitrary amount of data, use it, and decide how many items you want to consume. For example, if allows to write stuff like "ReadAWord" without double buffering: you get enough characters from the buffer until you find a space, and then you consume only the characters that are the space. "Steven Schveighoffer" , dans le message (digitalmars.D:167733), a écrit : > OK, so I had a couple partially written replies on the 'deprecating > std.stream etc' thread, then I had to go home. > > But I thought about this a lot last night, and some of the things Andrei > and others are saying is starting to make sense (I know!). Now I've > scrapped those replies and am thinking about redesigning my i/o package > (most of the code can stay intact). > > I'm a little undecided on some of the details, but here is what I think > makes sense: > > 1. We need a buffering input stream type. This must have additional > methods besides the range primitives, because doing one-at-a-time byte > reads is not going to cut it. > 2. I realized, buffering input stream of type T is actually an input range > of type T[]. Observe: > > struct /*or class*/ buffer(T) > { > T[] buf; > InputStream input; > ... > @property T[] front() { return buf; } > void popFront() {input.read(buf);} // flush existing buffer, read > next. > @property bool empty() { return buf.length == 0;} > } > > Roughly speaking, not all the details are handled, but this makes a > feasible input range that will perform quite nicely for things like > std.algorithm.copy. I haven't checked, but copy should be able to handle > transferring a range of type T[] to an output range with element type T, > if it's not able to, it should be made to work. Or with joiner(buffer); > I know at least, an > output stream with element type T supports putting T or T[]. What I think > really makes sense is to support: > > buffer!ubyte b; > outputStream o; > > o.put(b); // uses range primitives to put all the data to o, one element > (i.e. ubyte[]) of b at a time Of course, output stream should not have a consistent interface with input stream. > 3. An ultimate goal of the i/o streaming package should be to be able to > do this: > > auto x = new XmlParser(""); > > or at least > > auto x = new XmlParser(buffered("")); > > So I think arrays need to be able to be treated as a buffering streams. I > tried really hard to think of some way to make this work with my existing > system, but I don't think it will without unnecessary baggage, and losing > interoperability with existing range functions. A simple string stream can be built on top of a string, with no other member than the string itself, can't it ? With my definition of buffered stream, at least, it can, and any array could support: T[] front(size_t i) { return this[0..min(i, $)]; } void popFront(size_t i) { this = this[i..$]; } -- Christophe
Re: The more interesting question
"Jonathan M Davis" , dans le message (digitalmars.D:167901), a écrit : > On Friday, May 18, 2012 11:18:46 Steven Schveighoffer wrote: >> On Fri, 18 May 2012 11:05:21 -0400, Christophe Travert >> >> wrote: >> > "Steven Schveighoffer" , dans le message (digitalmars.D:167556), a >> > >> >> toStringz can allocate a new block in order to ensure 0 gets added. >> >> This >> >> is ludicrous! >> >> >> >> You are trying to tell me that any time I want to call a C function >> >> with a >> >> string literal, I have to first heap-allocate it, even though I *know* >> >> it's safe. >> > >> > How about "mystring\0".ptr ? >> >> AKA "mystring" :) >> >> I'm sorry, I don't see the reason to require this. All for the sake of >> making "" a null slice. I find the net gain quite trivial. > > And I find the net gain to be negative, since the fact that "" is non-null is > _useful_. > > - Jonathan M Davis I'm not saying "" should point to null. I'm saying people claiming that they have to heap-allocate (via toStringz) each time they call a c-function are just wrong. I tend to accept the point that making strings automatically zero terminated for making calls to c-function easier is not such a good idea, but I have no problem with [] !is "". Empty strings should be tested with empty. -- Christophe
Re: AliasTuples, rather than Records, to return multiple values
"Dario Schiavon" , dans le message (digitalmars.D:167822), a écrit : > void main() { >alias TypeTuple!(1, 2) a; >alias TypeTuple!(a, 3) b; // appends 3 to a >writeln(b); // prints "123" > } > > Appending items to tuples actually covers 99% of my needs of > single-item tuples in Python. Can anyone find other needs for > single-item tuples? Or for empty tuples? > >> >> However, another point of my post was whether we really need >> Records (std.typecons.Tuple's), which take approximately the >> same role as traditional struct's after all, to allow returning >> multiple values from functions. Wouldn't AliasTuples take on >> the role as well? Personally, I don't like Tuple unpacking automagically. It prevents creating a single element Typle, or a Tuple of Tuple, which may be harmful if Tuple gain a greater role than they have now. Creating a Tuple of Tuple is impossible or require heavy workarrounds with automagically unpacking Tuples, whereas with not automagically unpacking Tuples, it is very easy to introduce an unpacking syntax when necessary with a random unary operator. -- Christophe
Re: why D matters for Bioinformatics
"bearophile" , dans le message (digitalmars.D:168160), a écrit : > deadalnix: http://blog.thebird.nl/?p=93 >>>... >> I spreaded the word. This article is great and I 100% agree >> with it :D > > The article says: > >>There are a few things I miss in D. For example pattern >>recognition on unpacking data, which is great in Haskell, >>Erlang, and Scala (see example >>[http://www.scala-lang.org/node/120 ]).< > > The author of that article has missed that D lacks something much > simpler than pattern matching, and even more commonly useful. > Currently in D you have to write something like: > > int[2][] directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; > foreach (sx_sy; directions) { > immutable sx = sx_sy[0]; > immutable sy = sx_sy[1]; > // code that uses sx and sy > > > While a less clunky language allows you to unpack them better, > something like: > > auto directions = [tuple(-1, 0), tuple(1, 0), tuple(0, -1), > tuple(0, 1)]; > foreach (immutable (sx, sy); directions) { > // code that uses sx and sy > > > If you use tuples, you want to unpack them often, it's a basic > operation on tuples. This little example raises a question if tuples becomes part of the langage. Should static array have tuple capabilities ? Besides that, it is easy to emulate your example with a little library solution. Maybe something like that should be added to std.range. -- Christophe
Re: why D matters for Bioinformatics
"bearophile" , dans le message (digitalmars.D:168206), a écrit : >> Besides that, it is easy to emulate your example with a little >> library solution. Maybe something like that should be added to >> std.range. > > What syntax do you suggest? foreach (immutable sx, sy; unpack(s)) {...} doing something like (but more optimised than): foreach (immutable sx, sy; lockstep(s.map!"a[0]", s.map!"a[1]")) {...} You have to be careful with types, I don't think the result of map here can be cast to immutable (but I didn't check).
Re: synchronized (this[.classinfo]) in druntime and phobos
deadalnix , dans le message (digitalmars.D:169136), a écrit : > It open door for stuff like : > ReadWriteLock rw; > synchronized(rw.read) { > > } > > synchronized(rw.write) { > > } > > And many types of lock : spin lock, interprocesses locks, semaphores, . > . . And all can be used with the synchronized syntax, and without > exposing locking and unlocking primitives. > synchronize (foo) { ... } could do anything. You might as well propose a syntax sugar to call any function or method taking a delegate. You could make it do any operation with the idiom: struct MyType { void foo(void delegate() dg) { this.open(); scope(exit) this.close(); dg(); } ... } a.foo { // maybe some keyword should be necessary ... } Same problem as opApply, should take different type of delegates, etc...
Re: static array literal syntax request: auto x=[1,2,3]S;
Jonathan M Davis , dans le message (digitalmars.D:169705), a écrit : > auto found = find([1, 2, 3, 4, 5], 3); No problem if the rule could be the following: - array literals are static by default - array literals are copied to the heap when assigned to a dynamic array. - the former rule applies even if the array literal is assigned to a dynamic array via a function call, like it is the case in this example. - if a function can take both static and dynamic array as parameter, the static version takes precedence (it is always possible to call the dynamic version via slicing, at your own risk since the array is no longer valid at the end of the function call, or, much more wisely, by explicitely using the .dup property).
Re: static array literal syntax request: auto x=[1,2,3]S;
Artur Skawina , dans le message (digitalmars.D:169717), a écrit : > On 06/11/12 12:06, Christophe Travert wrote: >> Jonathan M Davis , dans le message (digitalmars.D:169705), a écrit : >>> auto found = find([1, 2, 3, 4, 5], 3); >> >> No problem if the rule could be the following: >> - array literals are static by default >> - array literals are copied to the heap when assigned to a dynamic >> array. >> - the former rule applies even if the array literal is assigned to a >> dynamic array via a function call, like it is the case in this example. >> - if a function can take both static and dynamic array as parameter, >> the static version takes precedence (it is always possible to call the >> dynamic version via slicing, at your own risk since the array is no >> longer valid at the end of the function call, or, much more wisely, by >> explicitely using the .dup property). > > T f(T)(T a) {} > > artur I don't see the problem. If T compiles with a static array, it returns a static array, there is no invalid pointer issue, since the returned value is not a slice, but a static array. -- Christophe
Re: static array literal syntax request: auto x=[1,2,3]S;
"Steven Schveighoffer" , dans le message (digitalmars.D:169718), a écrit : > Note that D1 was like this, [1,2,3] was auto-typed to int[3u]. It was a > constant source of pain that I would not like to revisit. Especially > since static arrays are now passed by value. > > -Steve Since static are passed by value, static arrays are now safe to use. This should decrease the pain a lot.
Re: Segmented Ranges?
"bearophile" , dans le message (digitalmars.D:169611), a écrit : > struct BilevelScan(Range) { This is basically std.algorithm.joiner > So an idea is to introduce in D the multi-level Ranges: > SegmentedInputRange > SegmentedOutputRange > SegmentedForwardRange > SegmentedBidirectionalRange > SegmentedRandomAccessRange > > I think a Segmented Input Range is more complex than an Input > Range. Inventing such ranges is like finding the miminal set of > axioms that allow to write down a theorem, that is to efficiently > implement the normal algorithms. This is not an easy for me, I > don't know much about this topic. I would only use this axiom: - a SegmentedRange defines a bySegment property, returning a Range of Range bearing the same element type as SegmentedRange. and define BySegment!Range as the type returned by range.bySegment, and SegmentType!Range as the type returned by range.bySegment.front. Then it is up to the implementer of the algorithm to test the type of a SegmentedRange (InputRange, OutputRange, etc.), the type of BySegment!Range, and the type of SegmentType!Range when needed. There are too many combinaisons to make specific range types for all of them. Moreover SegmentedRandomAccessRange would be ambiguous: is it a RandomAccessRange, a Range whose BySegment method returns a RandomAccessRange, a Range whose SegmentType is a RandomAccessRange, or all of them ? -- Christophe
Re: valid uses of shared
"Steven Schveighoffer" , dans le message (digitalmars.D:169568), a écrit : > On Thu, 07 Jun 2012 22:16:21 -0400, Robert DaSilva > wrote: > > >> You're forgetting about Global data. > > I wasn't so much forgetting it as I was ignoring it :) > > My thought on that is that the shared keyword in that case is truly a > storage class. It's the one place where having a value-type based shared > value makes sense. If we had some kind of synchronized/shared pairing, > the compiler would have to allocate mutex space for that too. > >> I think rather the head shared should be striped as this fits better >> with how D treats meaningless specifiers. > > I don't think we can do that with type constructors, but I'm not sure. > I'm certainly against it, as I am against the current abuses of that > methodology. > >> And trying to put structs that contain shared data on the stack should >> be illegal. The compiler can already heap-allocate function variables that should be on the stack. So why disallowing shared for function variables? void foo() { shared int test; // allocates test on shared memory block. } Just like: int delegate(int) adder(int a) { return b => (a+b); // allocates a on the heap to make a closure. } -- Christophe
Re: How to break const
Matthias Walter , dans le message (digitalmars.D:170036), a écrit : > On 06/18/2012 07:36 AM, Mehrdad wrote: >> Is it just me, or did I subvert the type system here? >> >> >> import std.stdio; >> >> struct Const >> { >> this(void delegate() increment) >> { this.increment = increment; } >> int a; >> void delegate() increment; >> void oops() const { this.increment(); } >> } >> >> void main() >> { >> Const c; >> c = Const({ c.a++; }); >> writeln(c.a); >> c.oops(); >> writeln(c.a); >> } >> > > I don't think so. When calling oops you have two references to the object c: > > - The this-pointer of the object itself which is not allowed to change > the object in the const-call. > - The reference from within main which is allowed to change it and can > be reached via the frame pointer of the delegate. > > I see this as perfectly valid code. Of course, opinions may differ here. But here, the frame pointer of the delegate is part of the const structure. By transitivity, the frame pointer should be const, and therefore, calling the delegate (which modifies the frame pointer) should not be legal. To be callable from a const method (oops), the member delegate (increment) should be of type "void delegate() const". This type exists, but is not easy to get[1]. Because constness of frame pointers is not implemented as it should be[2], there is a hole in the const system. [1] AFAIK, it can only be got by making an explicit delegate like &struct.const_method [2] Just like pure and no_throw attributes are complicated to work with delegates, the const attribute would be a mess to use at the moment. -- Christophe
Re: How to break const
Iain Buclaw , dans le message (digitalmars.D:170145), a écrit : > On 19 June 2012 09:18, Don Clugston wrote: >> So would I. Can you think of one? >> It was the best name I could come up with, given that the 'pure' was the >> keyword. >> We want a word that means 'no hidden state'. > > I thought that was what pure was for. :~) > > -- > Iain Buclaw > > *(p < e ? p++ : p) = (c & 0x0f) + '0'; A delegate does have a frame pointer, and it's not that well hidden. If you want no such 'hidden state', you do not want a delegate, you want a function pointer. That means all delegates are weakly pure (until they have an immutable frame pointer qualifier). If you want that this 'hidden state' does not change, that is another story. pure for a delegate could mean that the frame pointer does not change, but then, pure methods wouldn't allow you to make pure delegates: struct S { int i; int foo() pure { return i; } } S s; int delegate() pure dg = &s.foo; // error if pure change meaning when applied to a delegate
Re: Primary Ranges of Containers
Jonathan M Davis , dans le message (digitalmars.D:170054), a écrit : >> I'd propose to always add a bool template parameter (maybe isConst?) to >> the range since most of the write-functionality can be "removed" by a >> static if statement in order to make the range read-only. >> >> Any suggestions? Boolean parameters are very obscure. How do you guess what is the meaning of false in: Range!false; Range!IsConst.no would be better. > struct ArrayRange(bool isConst) {...} > alias ArrayRange!false Range; > alias ArrayRange!true ConstRange; Range and ConstRange seems a good thing to have, just like c++ containers have iterator and const_iterator.
Re: How to break const
Timon Gehr , dans le message (digitalmars.D:170178), a écrit : > That is completely unrelated. > It is impossible to justify transitivity of const for delegate context > pointers using this argument. It is far too general and the > justification for the general concept comes from a specific example > that is different from the one at hand. > > The question is, what the meaning of 'const' references should be: > > 1. data cannot be changed transitively through the reference > > 2. the reference can reference both 'const' and 'immutable' data and > 'immutable' data can transitively not be changed through the > reference. > > > 1. requires transitive const for delegate context pointers, 2. does not. A const reference can contain I don't understand the difference. struct Delegate(C, F, Args) { C* ptr; // points to some structure containing all referenced data R function(C*, Args) fun; R opCall(Args) { return ptr.fun(Args); } } The signature of opCall determines the type of the delegate. In reality, the delegate is opaque, and C is not typed. ptr is a pointer to void*, and fun knows how to use that pointer. But that does not prevent the pointer to be const or immutable. Note that calling opCall is not possible if the Delegate is const, or part of a const structure, because opCall does not have the const attribute. But the signature of opCall could have any kind of attributes: const, immutable, pure, nothrow, inout..., which can be reflected by the delegates type. A delegate of type "R delegate(Args) const" would be like this: struct DelegateConst(C, R, Args...) { const C* ptr; R function(const C*, Args) fun; R opCall(Args) const { return ptr.fun(Args); } } Now it is possible to call opCall if the DelegateConst is const. However, it is possible to build this delegate only if fun is const with regard to its context argument. The same holds if you replace const by immutable. Now, the context pointer can point to all type of data. C is a like a structure, and can contain any kind of data (mutable, const, immutable, shared...). However transitivity rules must be preserved. If the data is immutable, the delegate context pointer must be. If the data is a mix of mutable, const, and immutable data, there is no problem, has long has the function mutates only the mutable data (but then, the delegate's frame pointer type must be mutable, and the delegate is not callable if it is const). However, it must respect transitivity: if the delegate is immutable, all data contained in the context must be immutable. If the context pointer is const, the data can be mutable, const, or immutable. And where does all this comes from ? delegates are primarily methods applied to a struct or class instance. class S { data d; void method(arg); } S s = new S; void delegate(arg) dg = &s.method; The delegate is constructed directly from the object's method. (1) That is why it must have the same signature has objects method. If you want to fully represent methods, delegates must have all methods attributes: pure, nothrow, (which is a problem to introduce in toString methods, for example), etc... but also const, immutable, and maybe one day inout. Currently, delegates does not support all this. It makes life easier, because we do not have a zillion types of delegates, and it gives a little bit of air on const virality until we have proper systems to simplify all this. But this is a gap in the langage. It is up to the programmer to respect const transitivity, and not exploit this gap and break the langage. (1) In my first example, a langage delegate can be obtained from my artificial Delegate template by taking the adresse of opApply: Delegate!(C, R, Args) s; R delegate(Args) dg = &s.opApply (2) Note that it is however possible to obtain a 'C delegate(Args) const' but taking the adress of a const method. class S { data d; void method(Arg) const; } S s = new S; auto dg = &s.method; dg is infered as 'void delegate(Arg) const' by the compiler -- Christophe
Re: How to break const
Artur Skawina , dans le message (digitalmars.D:170175), a écrit : > On 06/19/12 15:29, deadalnix wrote: >> Le 19/06/2012 14:30, Artur Skawina a écrit : Due to D concept of weak purity, this doesn't ensure the required what we need here. >>> >>> Actually, it does - if it can be proved that the delegate can't alter the >>> object >>> via the context pointer (eg because whatever it points to is not mutable) >>> then >>> even D's "pure" is enough. Because the delegate would need to be passed a >>> mutable >>> ref to be able to alter the object, which then could hardly be constructed >>> as >>> "breaking" constness. >>> >>> But such a limit (const/immutable context) would be a problem for the cases >>> where >>> the delegate needs to alter some state (like export the result of some >>> operation), >>> but does _not_ modify the object it's embedded in. Note that the current >>> object may >>> very well be reachable (and mutable) from the delegate - but at some point >>> you have >>> to trust the programmer. Sure, fixing this hole would be great, but /how/ - >>> w/o >>> incurring unacceptable collateral damage? >>> >> >> This isn't a problem as long as the delegate isn't a member of the object. >> If it is, transitivity is broken, which is something you don't want. >> >> Relying on the trust on the programmer is a dumb idea. Human do mistake, way >> more than computers. The basic behavior MUST be a safe one. >> >> Transitivity has been introduced for good reason and language already >> provide a way to break it via cast. So it is unacceptable to rely on >> programmer on that point. >> It is possible to get the error when trying to call the delegate instead of preventing to make it const, as I said in the post you quote. It is probably a better solution. >>> >>> Any delegate? >>> >> >> No, any delegate that have type that isn't covariant with the expected >> delegate type. > >struct S { > int i; this(int i) { this.i = i; } > T* p; > void f(int i) { this.i = i; /*p.i++;*/ } >} >struct T { > int i; this(int i) { this.i = i; } > void delegate(int i) f; >} > >void main() { > auto t = new T(42); > auto s = new S(17); > s.p = t; > t.f = &s.f; > f(t); >} > >void f(const (T)* t) { > t.f(t.i*2); >} > > You're proposing to make the last 'f' function illegal, just because the > commented out part could happen. I'm saying that this is unlikely to happen > *by accident*, and of course would still be possible by casting away the > constness. > But banning "unsafe" delegates would result in casts *when using "safe" ones* > - which is not a real improvement because this would make the "bad" casts much > harder to spot. The proper way to do this is not cast, it is to give the proper constness for all types and methods : struct S { int i; this(int i) { this.i = i; } T* p; void f(int i) const { this.i = i; /*p.i++;*/ } // the commented part is illegal because S.f is const } struct T { int i; this(int i) { this.i = i; } void delegate(int i) const f; // T.f is const to be callable from .f } void main() { auto t = new T(42); auto s = new S(17); s.p = t; t.f = &s.f; // legal when T.f is const because S.f is also const f(t); } void f(const (T)* t) { t.f(t.i*2); // legal because T.f is const } -- Christophe
Re: How to break const
Christophe Travert, dans le message (digitalmars.D:170182), a écrit : > Timon Gehr , dans le message (digitalmars.D:170178), a écrit : >> That is completely unrelated. >> It is impossible to justify transitivity of const for delegate context >> pointers using this argument. It is far too general and the >> justification for the general concept comes from a specific example >> that is different from the one at hand. >> >> The question is, what the meaning of 'const' references should be: >> >> 1. data cannot be changed transitively through the reference >> >> 2. the reference can reference both 'const' and 'immutable' data and >> 'immutable' data can transitively not be changed through the >> reference. >> >> >> 1. requires transitive const for delegate context pointers, 2. does not. > > A const reference can contain > > I don't understand the difference. Apologies, I forgot to complete my post: A const reference can contain both mutable and immutable data, as long as it does not allow to mutate it. I don't understand point 2: if the reference contain const reference, it should not modify the const reference. Of course, immutable data should not be changed in any case. This is how I understand delegates should work: [the rest of the post is unchanged] > > struct Delegate(C, F, Args) > { > C* ptr; // points to some structure containing all referenced data > R function(C*, Args) fun; > R opCall(Args) { return ptr.fun(Args); } > } > > The signature of opCall determines the type of the delegate. In reality, > the delegate is opaque, and C is not typed. ptr is a pointer to void*, > and fun knows how to use that pointer. But that does not prevent the > pointer to be const or immutable. > > Note that calling opCall is not possible if the Delegate is const, or > part of a const structure, because opCall does not have the const > attribute. > > But the signature of opCall could have any kind of attributes: const, > immutable, pure, nothrow, inout..., which can be reflected by the > delegates type. > > A delegate of type "R delegate(Args) const" would be like this: > > struct DelegateConst(C, R, Args...) > { > const C* ptr; > R function(const C*, Args) fun; > R opCall(Args) const { return ptr.fun(Args); } > } > > Now it is possible to call opCall if the DelegateConst is const. > However, it is possible to build this delegate only if fun is const with > regard to its context argument. > > The same holds if you replace const by immutable. > > Now, the context pointer can point to all type of data. C is a like a > structure, and can contain any kind of data (mutable, const, immutable, > shared...). However transitivity rules must be preserved. If the data is > immutable, the delegate context pointer must be. If the data is a mix of > mutable, const, and immutable data, there is no problem, has long has > the function mutates only the mutable data (but then, the delegate's > frame pointer type must be mutable, and the delegate is not callable if > it is const). > > However, it must respect transitivity: if the delegate is immutable, all > data contained in the context must be immutable. If the context pointer > is const, the data can be mutable, const, or immutable. > > > And where does all this comes from ? > delegates are primarily methods applied to a struct or class instance. > > class S > { > data d; > void method(arg); > } > > S s = new S; > void delegate(arg) dg = &s.method; > > The delegate is constructed directly from the object's method. (1) > > That is why it must have the same signature has objects method. If you > want to fully represent methods, delegates must have all methods > attributes: pure, nothrow, (which is a problem to introduce in > toString methods, for example), etc... but also const, immutable, and > maybe one day inout. > > Currently, delegates does not support all this. It makes life easier, > because we do not have a zillion types of delegates, and it gives a > little bit of air on const virality until we have proper systems to > simplify all this. But this is a gap in the langage. It is up to the > programmer to respect const transitivity, and not exploit this gap and > break the langage. > > (1) In my first example, a langage delegate can be obtained from my > artificial Delegate template by taking the adresse of opApply: > > Delegate!(C, R, Args) s; > R delegate(Args) dg = &s.opApply > > (2) Note that it is however possible to obtain a 'C delegate(Args) > const' but taking the adress of a const method. > > class S > { > data d; > void method(Arg) const; > } > > S s = new S; > auto dg = &s.method; > > dg is infered as 'void delegate(Arg) const' by the compiler > > -- > Christophe
Re: How to break const
Timon Gehr , dans le message (digitalmars.D:170185), a écrit : > On 06/19/2012 07:18 PM, Christophe Travert wrote: > In 2., mutable data referred to by a const reference might be changed > through it. Then it is not a const reference, it is a normal reference. Qualifying the reference as 'const' does not by you anything at all. Of course it should not change immutable data, no delegate should do that ! You might state that frame pointer are opaque, have no const qualifier, and that any delegates is callable even if they are in a const structure. But saying a reference (frame pointer) can be const AND modify data is a bit weird. -- Christophe
Re: How to break const
Artur Skawina , dans le message (digitalmars.D:170191), a écrit : >> The proper way to do this is not cast, it is to give the proper >> constness for all types and methods : >> >>struct S { >> int i; this(int i) { this.i = i; } >> T* p; >> void f(int i) const { this.i = i; /*p.i++;*/ } >> // the commented part is illegal because S.f is const I missed that. Then I disagree with your use of delegates. The solution here is to make the free f function to take a non const T*, since the function tries to modify variables from that T instance (via a delegate). I don't mind if the delegate modify an S or a T instance: it should not modify anything. You use a delegate to store an S structure that you can modifiy in a const T. This is only one step to consider the following acceptable: struct T { ref int delegate() _i; this(int i_) { _i = () => i_ } @property ref int i() const { return _i(); } } void foo(const T*) { T.i = 10; } Would you say that this is not a problem, since you do not modify a T instance with that delegate? I prefer to legalise: struct T { mutable int i; // makes modifying i legal, but prevent instanciating // an immutable T and several optimizations } > Only a const T.f would be pointless. I would allows you to safely call that delegate from a const T instance. > Like I've already said twice in this thread - it *can* be done (the > function has to be "pure" too for it to work), but certain delegate > uses, which are OK now, would be forbidden. > > I'm all for fixing this hole - it's just that the proposed "fix" would > have consequences, which can't simply be ignored. > > Language design is not a game of whack-a-mole, where you ban random > features left and right, because you think you've noticed a problem, > without properly identifying it nor spending even a single second > figuring out the implications. I completely agree with that. And I'm fine with the current situation: you trust the programmer not to use delegates to break the const system until we have a proper system to deal with this (inference of constness of delegates, for a start). But encouraging the use of delegates to modify variables from a const instance seems wrong to me. -- Christophe
Re: How to break const
deadalnix , dans le message (digitalmars.D:170262), a écrit : > Once again, this is inconsistent with how purity is defined elsewhere. > >> I'm all for fixing this hole - it's just that the proposed "fix" would >> have consequences, which can't simply be ignored. >> > > They are not ignored, but it seems you don't clearly understand the > implications and the big picture. Let me stress that argument: It has been said in several places in the newsgroup that D had many features that work right, but now problems emerge when feature interacts with each other. That is why consistency in the langage design is *very* important. Implementing delegates properly with constness of the context pointer, and a consistent pure attribute is a way that garanties that delegates interacts finely with all other feature (constness, purity, etc.): delegates are implementable has classique structures with an opApply like I showed in a post in this thread. There is no black magic (1), an thus delegates are guaranted not add any hole in the langage: delegates are only a very convenient way to do what you can do with a context pointer and a function pointer. (1) there is still a little bit of magic: closure makes automatic heap allocation of normally stack variables. Since you could do that explicitely, this is no big problem. The only way to make this magic disappear is to make heap allocation of normally stack variables legal in other cases, which would cover another big hole in the langage but this is too big a change in the langage to be accepted for the moment. -- Christophe
Re: How to break const
deadalnix , dans le message (digitalmars.D:170272), a écrit : > Le 20/06/2012 10:18, Christophe Travert a écrit : > What is the conclusion here ? I think it is the same as yours. My conclusion is that delegate's signature should be the same has methods's signature, and have the same meaning. That makes the langage consistent, and makes sure not to bring any new hole in the langage. In detail, they should be able to have a const or immutable attribute for their frame pointer, just like methods can be const or immutable with regard to the this argument, and a pure attribute that has the same meaning as methods pure attribute: no global variable access (and no impure function calls), but possibility to access the frame pointer. -- Christophe
Re: How to break const
Timon Gehr , dans le message (digitalmars.D:170288), a écrit : > On 06/20/2012 09:16 AM, deadalnix wrote: >> Le 19/06/2012 17:49, Timon Gehr a écrit : >>> >>> The question is, what the meaning of 'const' references should be: >>> >>> 1. data cannot be changed transitively through the reference >>> >>> 2. the reference can reference both 'const' and 'immutable' data and >>> 'immutable' data can transitively not be changed through the >>> reference. >>> >>> >>> 1. requires transitive const for delegate context pointers, 2. does not. >>> >> >> No, 2. require 1., even if the initialization is broken. >> >> class Foo { >> void delegate() dg; >> >> this(immutable void delegate() dg) immutable { >> thid.dg = dg; >> } >> } >> >> Now, as delegate doesn't carry the constness of its context, an >> immutable instance of Foo can refers to something that isn't immutable. > > Clarification: 'const' means 'const'. No other qualifiers. > > There is no 'const' in that example code. 'immutable' obviously needs to > be transitive regardless of the particular interpretation of 'const'. > const means: maybe immutable. Thus const needs to be transitive too. If you apply different rules to const and to immutable, you are breaking the consistency of the langage.
Re: How to break const
Timon Gehr , dans le message (digitalmars.D:170296), a écrit : > On 06/20/2012 01:36 PM, Christophe Travert wrote: >> Timon Gehr , dans le message (digitalmars.D:170288), a écrit : >>> On 06/20/2012 09:16 AM, deadalnix wrote: >>>> Le 19/06/2012 17:49, Timon Gehr a écrit : >>>>> >>>>> The question is, what the meaning of 'const' references should be: >>>>> >>>>> 1. data cannot be changed transitively through the reference >>>>> >>>>> 2. the reference can reference both 'const' and 'immutable' data and >>>>> 'immutable' data can transitively not be changed through the >>>>> reference. >>>>> >>>>> >>>>> 1. requires transitive const for delegate context pointers, 2. does not. >>>>> >>>> >>>> No, 2. require 1., even if the initialization is broken. >>>> >>>> class Foo { >>>> void delegate() dg; >>>> >>>> this(immutable void delegate() dg) immutable { >>>> thid.dg = dg; >>>> } >>>> } >>>> >>>> Now, as delegate doesn't carry the constness of its context, an >>>> immutable instance of Foo can refers to something that isn't immutable. >>> >>> Clarification: 'const' means 'const'. No other qualifiers. >>> >>> There is no 'const' in that example code. 'immutable' obviously needs to >>> be transitive regardless of the particular interpretation of 'const'. >>> >> const means: maybe immutable. > > Or maybe mutable. Therefore, interpretation '2.' > >> Thus const needs to be transitive too. > > Wrong. This is the (A==>B) ==> (B==>A) fallacy, where > > A: 'const' is transitive > B: 'const' references cannot modify 'immutable' data > > The conclusion regarding transitivity, given interpretation 2., is that > 'const' needs to be transitive _if_ it is actually 'immutable'. OK, I understand what you mean. I think this behavior is dangerous. You have to add rules to make sure B is not violated. I am not sure this is worth it. >> If you apply different rules to const and to immutable, you are breaking >> the consistency of the langage. >> > > Certainly not. This is like saying that applying different rules to > 'const' and mutable is breaking the consistency of the language. > mutable is not transitive. OK, that was not the right argument. Const is transitive according to the spec. Making it not transitive would break the mutable < const < immutable design. You're trying to make an exception for data hidden behind a delegate, which is a dangerous thing. -- Christophe
Re: How to break const
Artur Skawina , dans le message (digitalmars.D:170305), a écrit : > On 06/20/12 09:31, Christophe Travert wrote: >> Artur Skawina , dans le message (digitalmars.D:170191), a écrit : >>>> The proper way to do this is not cast, it is to give the proper >>>> constness for all types and methods : >>>> >>>>struct S { >>>> int i; this(int i) { this.i = i; } >>>> T* p; >>>> void f(int i) const { this.i = i; /*p.i++;*/ } >>>> // the commented part is illegal because S.f is const >> >> I missed that. Then I disagree with your use of delegates. The solution >> here is to make the free f function to take a non const T*, since the >> function tries to modify variables from that T instance (via a >> delegate). > > The issue is the delegate definition - what exactly is a delegate? > My first message in this thread started with this sentence: > "It's fine, if you view a delegate as opaque". Because if you do, then > accesses via a delegates context pointer are no different from using > an external reference. That message also contained a program to show > how trivial bypassing const is, at least for impure functions. So > banning "unsafe" delegates wouldn't really change the situation. Having opaque delegate and no holes in the langage is a possibility, but not without changing the langage spec. With opaque delegates, you can't have a pure delegate, only pure function pointers, because pure means you do not access external data, and accessing data pointed by a the opaque frame pointer of the delegate would be accessing external data. You may then redefine pure for delegate as delegates having only immutable external data (const is not enough). I'm definitely not in favor of that solution.
Re: How to break const
Timon Gehr , dans le message (digitalmars.D:170313), a écrit : >> OK, I understand what you mean. I think this behavior is dangerous. You >> have to add rules to make sure B is not violated. I am not sure this is >> worth it. >> > > No additional rules necessary, only loosening of existing rules. (in > the obvious design, not in how the compiler implements it.) > If you apply different rules to const and to immutable, you are breaking the consistency of the langage. >>> >>> Certainly not. This is like saying that applying different rules to >>> 'const' and mutable is breaking the consistency of the language. >>> mutable is not transitive. >> >> OK, that was not the right argument. Const is transitive according to >> the spec. > > IIRC the spec is based on the claim that this is necessary in order to > maintain immutability guarantees, so this is irrelevant for discussions > relating to the alternative interpretation. > The spec, by saying it is necessary to maintain immutability, has its own reasons. In reality, it is not purely necessary to maintain immutability, as you say, but it is necessary to maintain immutability AND the benefit of purity, AND the integrity of the whole spec, which is based on the assumption that const is transitive. >> Making it not transitive would break the mutable< const< immutable design. > > What is this '<' relation ? I meant that const is somewhere between mutable and immutable. Also, mutable variable can contains const and immutable data, const variable can contain immutable data, but no mutable data (meaning: mutable through this reference). Subparts of a reference can increase the protection level of the data, but not decrease it. >> You're trying to make an exception for data hidden >> behind a delegate, which is a dangerous thing. >> > I understand the implications. There is nothing dangerous about it. Making exception in the langage is dangerous because you can have surprising results when interacting with other exception. Give some spec explaining how your opaque delegate works, and someone will find a hole one day. I can't think of a way of implementing opaque delegates without a hole inside, or without loosing most of the point of immutability and purity. If you do, please share.
Re: Add := digraph to D
"Lars T. Kyllingstad" , dans le message (digitalmars.D:170370), a >>> auto name = initializer; >>> const name = initializer; >>> immutable name = initializer; >>> shared name = initializer; >>> enum name = initializer; >> >> After the first aren't these all just short hand for "const >> auto name = init" etc with the given keyword? > > No. Many don't realise this, but "auto" doesn't actually stand > for "automatic type inference". It is a storage class, like > static, extern, etc., and it means that the variable stops > existing at the end of the scope. It is, however, the default > storage class, which is why it is useful when all you want is > type inference. Even C has auto. auto is called a storage class, but where did you read that auto would make the variable stop existing at the end of the scope ? int delegate(int) add(int i, int j) { auto k = i+j; return a => a+k; } Are you sure k will stop existing at the end of the scope ? I have no compiler at hand to check this, but I would be very surprised. Object Create() { auto k = new Object(); return k; } Same question. -- Christophe
Re: Add := digraph to D
"Lars T. Kyllingstad" , dans le message (digitalmars.D:170381), a > That said, I was still wrong. :) I just tried it now, and > apparently you can write pointless stuff like "auto extern int > foo;" and DMD will compile it just fine. (And, unless that is a > bug, it means D has redefined 'auto' to mean absolutely nothing, > except to be a marker saying "this is a declaration", allowing > one to omit both storage class and type.) dlang.org states: | Auto Attribute | The auto attribute is used when there are no other attributes and type | inference is desired. So indeed, in a variable declaration, auto has no meaning other than saying "this is a declaration". -- Christophe
Re: LinkedList and Deque API in DCollections
d coder , dans le message (digitalmars.D:170392), a écrit : > --f46d04083de73dc5ab04c2fb032c > Content-Type: text/plain; charset=ISO-8859-1 > > Greetings > > I do not know if this email belongs to this list or not. It is about a > package available at dsource. Let me know if I should be posting such > emails elsewhere. > > I need an implementation of Doubly Linked list and Deque. Since the > std.container library does not support these containers yet, I am trying to > use these from DCollections library available on DSource here > http://www.dsource.org/projects/dcollections . I am using the D2 branch of > this library. > > Now the issue is, I could not find any API for adding elements to the > beginning of the LinkList. In the documentation (which is there for D1 > version) I see methods like append and prepend, but I do not think these > are implemented in the D2 version of the library. And I see that for Deque, > only prepend is implemented, there is no append. > > Also it is not clear to me if the "add" method could be used to append > elements on the back of the LinkList or Deque collection. Did you try : list.insert(list.begin, value);
Re: LinkedList and Deque API in DCollections
d coder , dans le message (digitalmars.D:170396), a écrit : > --e0cb4efe2a2821373604c2fc4a15 > Content-Type: text/plain; charset=ISO-8859-1 > >> >> >> Did you try : list.insert(list.begin, value); >> > > Thanks. But I believe append and prepend are too common use-cases to be > ignored. > > Also, I see that "assert(some-expression)" has been used at a lot of places > in the code of the libraries. Since these libraries are built with -release > option, should these assert statements not be replaced by "enforce"? > auto append(T, V)(ref T t, V value) { return t.insert(t.begin, value); } should do the trick. (you may add a guard to make sure T is a container)
Re: csvReader read file byLine()?
Jens Mueller , dans le message (digitalmars.D:170448), a écrit : > Jesse Phillips wrote: >> On Friday, 22 June 2012 at 08:12:59 UTC, Jens Mueller wrote: >> >The last line throws a CSVException due to some conversion error >> >'Floating point conversion error for input "".' for the attached >> >input. >> > >> >If you change the input to >> >3.0 >> >4.0 >> >you get no exception but wrong a output of >> >[[4], [4]] >> >. >> >> Yes, and it seems .joiner isn't as lazy as I'd have thought. >> byLine() reuses its buffer so it will overwrite previous lines in >> the file. This can be resolved by mapping a dup to it. >> >> import std.stdio; >> import std.algorithm; >> import std.csv; >> >> void main() >> { >> struct Record { >> double one; >> } >> auto filename = "file.csv"; >> auto file = File(filename, "r"); >> auto input = map!(a => a.idup)(file.byLine()).joiner("\n"); >> auto records = csvReader!Record(input); >> foreach(r; records) >> { >> writeln(r); >> } >> } > > Thanks. That works. But this should be either mentioned in the > documentation or fixed. I would prefer a fix because the code above > looks like a work around. Probably byLine or joiner then need some > fixing. What do you think? > > Jens Yes, and that increases GC usage a lot. Looking at the implementation, joiner as a behavior that is incompatible with ranges reusing some buffer: joiner immidiately call's the range of range's popFront after having taken its front range. Instead, it should wait until it is necessary before calling popFront (at least until all the data has be read by the next tool of the chain). Fixing this should not be very hard. Is there an issue preventing to make this change? -- Christophe
Re: An idea to avoid a narrow class of bugs
Alex Rønne Petersen , dans le message (digitalmars.D:170616), a écrit : >> >> To me, it is a GC implementation issue. You should be able to allocate >> in destructors. > > Yes, I don't understand why on earth this limitation is in place. There > is no (good) technical reason it should be there. Allowing safe unwinding of the stack when throwing exceptions is not a 'good' reason ? Well, a destructor should rather be no_throw, and should be able to call no_throw (GC) functions. -- Christophe
Re: An idea to avoid a narrow class of bugs
Alex Rønne Petersen , dans le message (digitalmars.D:170622), a écrit : > On 25-06-2012 15:27, Christophe Travert wrote: >> Alex Rønne Petersen , dans le message (digitalmars.D:170616), a écrit : >>>> >>>> To me, it is a GC implementation issue. You should be able to allocate >>>> in destructors. >>> >>> Yes, I don't understand why on earth this limitation is in place. There >>> is no (good) technical reason it should be there. >> >> Allowing safe unwinding of the stack when throwing exceptions is not a >> 'good' reason ? >> >> Well, a destructor should rather be no_throw, and should be able to >> call no_throw (GC) functions. >> > > I fail to see how this is a problem. The GC (read: finalizer thread) > simply has to catch the exception and Do Something Meaningful with it. > I confused two dinstict issues concerning destructors: - 1/ during GC, the GC may collect data in any order. References in a collected object may be invalid. This is not specific to GC usage in destructors however. Example: struct B { string x; ~this() { writeln('Deleting ', x); } } struct A { B* b; ~this() { writeln('About to Delete ', b.x); // error since b may have been // deleted before this A instance. GC.free(b); // should be fine, GC.free has no effect on already // deallocated block } } => Using a maybe dead reference should be forbidden in destructor: Problem: it is in the general case impossible to tell if A.b has been allocated from the GC or not at compile time. However, somebody trying to collect GC data in a destructor is likely to ignore that the data may already have been collected. I reckon it is legitimate to collect GC data from a destructor (provided issue 2 is handled). - 2/ When an exception is thrown, destructors are called to unwind the stack until the exception is caught. If destructors start to trigger exceptions, things can get really messy. => It is a good idea to make destructors no_throw (and as stupid and simple as possible). Maybe there is a third issue that motivate bearophile's post. -- Christophe
Re: Get rid of isInfinite()?
"Mehrdad" , dans le message (digitalmars.D:170697), a écrit : > On Monday, 25 June 2012 at 23:03:59 UTC, Jonathan M Davis wrote: >> You could store those elements internally as you iterate over >> them > > That's *precisely* the point of my wrapper... sorry if that > wasn't clear. > > Why shouldn't that be sufficient for making it random-access? > > >> If you can somehow figure out how to do that via buffering, >> then you could make it a forward range as well as whatever >> other range types you could define the functions for, but you'd >> have to figure out a way to define save. > > > > OK, now we're at the same place. :P > > What I'm saying is, I __CAN__ get the buffering to work. > > What I __cannot__ figure out what to do with is 'length'... I > can't think of anything reasonable to return, since it's not > infinite (which I might represent as ulong.max, if anything) but > it's unbounded. > > > So the _ONLY_ problem I'm running into right now is length() -- > any ideas how I could fix that? I you are planning to buffer all input as you read it to enable random-access, then you should better read the whole file in your buffer from the start and get done with it, since you will end up having read the whole file in the buffer at the end... -- Christophe
Re: Get rid of isInfinite()?
Christophe Travert, dans le message (digitalmars.D:170706), a écrit : > "Mehrdad" , dans le message (digitalmars.D:170697), a écrit : >> On Monday, 25 June 2012 at 23:03:59 UTC, Jonathan M Davis wrote: >>> You could store those elements internally as you iterate over >>> them >> >> That's *precisely* the point of my wrapper... sorry if that >> wasn't clear. >> >> Why shouldn't that be sufficient for making it random-access? >> >> >>> If you can somehow figure out how to do that via buffering, >>> then you could make it a forward range as well as whatever >>> other range types you could define the functions for, but you'd >>> have to figure out a way to define save. >> >> >> >> OK, now we're at the same place. :P >> >> What I'm saying is, I __CAN__ get the buffering to work. >> >> What I __cannot__ figure out what to do with is 'length'... I >> can't think of anything reasonable to return, since it's not >> infinite (which I might represent as ulong.max, if anything) but >> it's unbounded. >> >> >> So the _ONLY_ problem I'm running into right now is length() -- >> any ideas how I could fix that? > > I you are planning to buffer all input as you read it to enable > random-access, then you should better read the whole file in your buffer > from the start and get done with it, since you will end up having read > the whole file in the buffer at the end... > Hum, sorry, that is irrelevant if your input is not a file.
Re: const ref in opAssign
"monarch_dodra" , dans le message (digitalmars.D:170728), a écrit : > Thanks for the in-depth explanation! It is still not very clear > to me, mostly because I don't understand "you wouldn't be able to > assign a const member variable's value of rhs to a non-const > member variable of this". This works fine in C++. Must be because > I've never used a language with reference semantics before. In D, const is transitive. It mean that if an instance is const, everything that is refered by this instance is const. Thus, I can't copy a reference of a const instance to make a non-const reference. Example: struct A { int* x; ref A opAssign(const ref other) { this.x = other.x; // error: other.x, which is a const(int*) because // of transitivity, can't be assign to this.x // (which is a non-const int*). } } You need to write either: ref A opAssign(ref other) { this.x = other.x; // no problem, other.x is not const. // Note: the value this.x is now shared between this and other } or: ref A opAssign(const ref other) // deep copy { this.x = new int(*other.x); // this is a deep copy: a new int is created to take other.x's value } If the structure contain no references at all, there is no problem, and opAssign should be const ref, with a const overload for l-values, as previously said. -- Christophe
Re: standard ranges
"Jonathan M Davis" , dans le message (digitalmars.D:170852), a écrit : > completely consistent with regards to how it treats strings. The _only_ > inconsintencies are between the language and the library - namely how foreach > iterates on code units by default and the fact that while the language > defines > length, slicing, and random-access operations for strings, the library > effectively does not consider strings to have them. char[] is not treated as an array by the library, and is not treated as a RandomAccessRange. That is a second inconsistency, and it would be avoided is string were a struct. I won't repeat arguments that were already said, but if it matters, to me, things should be such that: - string is a druntime defined struct, with an undelying immutable(char)[]. It is a BidirectionalRange of dchar. Slicing is provided for convenience, but not as opSlice, since it is not O(1), but as a method with a separate name. Direct access to the underlying char[]/ubyte[] is provided. - similar structs are provided to hold underlying const(char)[] and char[] - similar structs are provided for wstring - dstring is a druntime defined alias to dchar[] or a struct with the same functionalities for consistency with narrow string being struct. - All those structs may be provided as a template. struct string(T = immutable(char)) {...} alias string(immutable(wchar)) wstring; alias string(immutable(dchar)) dstring; string(const(char)) and string(char) ... are the other types of strings. - this string template could also be defined as a wrapper to convert any range of char/wchar into a range of dchar. That does not need to be in druntime. Only types necessary for string litterals should be in druntime. - string should not be convertible to char*. Use toStringz to interface with c code, or the underlying char[] if you know you it is zero-terminated, at you own risk. Only string litterals need to be convertible to char*, and I would say that they should be zero-terminated only when they are directly used as char*, to allow the compiler to optimize memory. - char /may/ disappear in favor of ubyte (or the contrary, or one could alias the other), if there is no other need to keep separate types that having strings that are different from ubyte[]. Only dchar is necessary, and it could just be called char. That is ideal to me. Of course, I understand code compatibility is important, and compromises have to be made. The current situation is a compromise, but I don't like it because it is a WAT for every newcomer. But the last point, for example, would bring no more that code breakage. Such code breakage may make us find bugs however... -- Christophe
Re: standard ranges
Jonathan M Davis , dans le message (digitalmars.D:170872), a écrit : > On Thursday, June 28, 2012 08:05:19 Christophe Travert wrote: >> "Jonathan M Davis" , dans le message (digitalmars.D:170852), a écrit : >> > completely consistent with regards to how it treats strings. The _only_ >> > inconsintencies are between the language and the library - namely how >> > foreach iterates on code units by default and the fact that while the >> > language defines length, slicing, and random-access operations for >> > strings, the library effectively does not consider strings to have them. > >> char[] is not treated as an array by the library > > Phobos _does_ treat char[] as an array. isDynamicArray!(char[]) is true, and > char[] works with the functions in std.array. It's just that they're all > special-cased appropriately to handle narrow strings properly. What it > doesn't > do is treat char[] as a range of char. > >> and is not treated as a RandomAccessRange. All arrays are treated as RandomAccessRanges, except for char[] and wchar[]. So I think I am entitled to say that strings are not treated as arrays. An I would say I am also entitle to say strings are not normal ranges, since they define length, but have isLength as true, and define opIndex and opSlice, but are not RandomAccessRanges. The fact that isDynamicArray!(char[]) is true, but isRandomAccessRange is not is just another aspect of the schizophrenia. The behavior of a templated function on a string will depend on which was used as a guard. > > Which is what I already said. > >> That is a second inconsistency, and it would be avoided is string were a > struct. > > No, it wouldn't. It is _impossible_ to implement length, slicing, and > indexing > for UTF-8 and UTF-16 strings in O(1). Whether you're using an array or a > struct to represent them is irrelevant. And if you can't do those operations > in O(1), then they can't be random access ranges. I never said strings should support length and slicing. I even said they should not. foreach is inconsistent with the way strings are treated in phobos, but opIndex, opSlice and length, are inconsistent to. string[0] and string.front do not even return the same Please read my post a little bit more carefully before answering them. About the rest of your post, I basically say the same as you in shorter terms, except that I am in favor of changing things (but I didn't even said they should be changed in my conclusion). newcomers are troubled by this problem, and I think it is important. They will make mistakes when using both array and range functions on strings in the same algorithm, or when using array functions without knowing about utf8 encoding issues (the fact that array functions are also valid range functions if not for strings does not help). But I also think experienced programmers can be affected, because of inattention, reusing codes written by inexperienced programmers, or inappropriate template guards usage. As a more general comment, I think having a consistent langage is a very important goal to achieve when designing a langage. It makes everything simpler, from langage design to user through compiler and library development. It may not be too late for D. -- Christophe
Re: standard ranges
"David Nadlinger" , dans le message (digitalmars.D:170875), a écrit : > On Thursday, 28 June 2012 at 09:49:19 UTC, Jonathan M Davis wrote: >>> char[] is not treated as an array by the library, and is not >>> treated as a RandomAccessRange. That is a second >>> inconsistency, and it would be avoided is string were a struct. >> >> So, it looked to me like you were saying that making string a >> struct would >> make it so that it was a random access range, which would mean >> implementing >> length, opSlice, and opIndex. > > I think he meant that the problem would be solved because people > would be less likely to expect it to be a random access range in > the first place. Yes.
Re: standard ranges
Timon Gehr , dans le message (digitalmars.D:170884), a écrit : >> An I would say I am also entitle to say strings are not normal >> ranges, since they define length, but have isLength as true, > > hasLength as false. Of course, my mistake. > They define length, but it is not part of the range interface. > > It is analogous to the following: > [...] I consider this bad design. >> and define opIndex and opSlice, > > [] and [..] operate on code units, but for a random access range as > defined by Phobos, they would not. A bidirectional range of dchar with additional methods of a random access range of char. That is what I call schizophrenic. >> but are not RandomAccessRanges. >> >> The fact that isDynamicArray!(char[]) is true, but >> isRandomAccessRange is not is just another aspect of the schizophrenia. >> The behavior of a templated function on a string will depend on which >> was used as a guard. >> > No, it won't. Take the isRandomAccessRange specialization of an algorithm in Phobos, replace the guard by isDynamicArray, and you are very likely to change the behavior, if you do not simply break the function. > When read carefully, the conclusion says that code compatibility is > important only a couple sentences before it says that breaking code for > the fun of it may be a good thing. It was intended as a side-note, not a conclusion. Sorry for not being clear. >> newcomers are troubled by this problem, and I think it is important. > > Newcomers sometimes become seasoned D programmers. Sometimes they know > what Unicode is about even before that. I knew what unicode was before coming to D. But, strings being arrays, I suspected myString.front would return the same as myString[0], i.e., a char, and that it was my job to make sure my algorithms were valid for UTF-8 encoding if I wanted to support it. Most of the time, in langage without such UTF-8 support, they are without much troubles. Code units matters more than code points most of the time. > The language is consistent here. The library treats some language > features specially. It is not the language that is "confusing". The > whole reason to introduce the library behaviour is probably based on > similar reasoning as given in your post. OK, I should have said the standard library is inconsistent (with the langage). > The special casing has not caused me any trouble, and sometimes it was > useful. Of course, I can live with that.
Re: Creating a Sub-view of a non - RA (hasSlicing) range.
Have you had a look at dcollection ? http://www.dsource.org/projects/dcollections There is a doubly linked list implementation, with range and cursors (entities that have iterator functionalities).
Re: foreach and retro
"bearophile" , dans le message (digitalmars.D:171013), a écrit : > It's not a bug, it's caused by how ranges like retro work. retro > yields a single item. In D you can't overload on return values, But you can overload OpApply. -- Christophe
Re: Proposal: takeFront and takeBack
takeFront implementation is dangerous for ranges which invalidates their front value when popFront is called, for instance, File.byLine. Thus takeFront will have to be used with care: any range implement takeFront (because of the template and USFC), but it may not be valid. That makes the range interface more complicated: There is a takeFront property, but you have to check it is safe to use... how do you check that by the way? Are there so many ranges that duplicate efforts in front and popFront, where there is no easy workarround? Would it be such an improvement in performance to add takeFront? strings being immutable, my guess would be that it is not that hard for the compiler to optimize a foreach loop (it should be easy to profile this...). Even if there is an efficiency issue, you could easily solve it by implementing a range wrapper or an opApply method to make foreach faster. -- Christophe
Re: Proposal: takeFront and takeBack
Range have been designed with the idea that front is valid until next call to popFront. If popFront was to be called right after front, then it would just be a popFront that returns a value, and maybe a justPop or something if you don't want to copy the value. It's delicate to come now and decide that if a previously returned front value is no longer valid after a call to popFront, it should be documented by an enum. Who is going to review all libraries (written and to come) to make sure the enum is placed when it should be? The reverse should be done instead: if you want you range to be optimized by calling takeFront, define something (for example... takeFront). Then use algorithms that are specialized for takeFront.
Re: Pure functions and pointers (yes, again)
Denis Shelomovskij , dans le message (digitalmars.D:171072), a écrit : > Since issue 8185 has been closed, I'm still very confused. I just > understood that endless discussion doesn't result in anything. > > See example from http://d.puremagic.com/issues/show_bug.cgi?id=8185#c40 > --- > int f(size_t p) pure > { > return *cast(int*) p; > } > > void g(size_t p, ref size_t) pure > { > ++*cast(int*) p; > } > > void h(size_t a, size_t b) pure > { > int res = f(a); > g(b, b); > assert(res == f(a)); // may fail, no guaranties by language! > } > > void main() > { > int a; > h(cast(size_t) &a, cast(size_t) &a); > } > --- > > Jonathan M Davis (whose documentation correcting pull closed the issue) > told me that this code result in undefined behaviour. What _exectly_ > language rule this violates? I don't see this rule, but if there is no > such rule, how can we treat anything as strongly pure function? Casting a value to a pointer clearly subvert the type system. A value with no reference (like size_t) is convertible to immutable, but a pointer cannot. Thus, a function with f's signature is strongly pure, but it is not if takes an int*. That's how you can create a bug. The same issue occurs if you 'create' a pointer by illegal pointer arithmetic for instance: this is undefined behavior. Creating and using any kind reference by casting is undefined. That's not only a purity problem, it is a safety, a garbage collection, an and optimisation issue. In your case, you know what you ar doing regarding safety and garbage collection. Fine. But you do not respect optimisations linked with purity. Too bad, you can't ignore that the compiler is going to make assumptions regarding your datas types. What change would you expect in the langage? making pure function automatically @safe? That may not be such a ba idea. However, that is not even enough, you could still create bug from optimizations with casting outside the pure function (for instance, you could alias variables that should not be aliased). The only possibility to completely avoid this kind of bug is to forbid either optimization or casting. That's not going to happen in D.
Re: Proposal: takeFront and takeBack
"Roman D. Boiko" , dans le message (digitalmars.D:171108), a écrit : > > What is error-prone in current client code? > > If particular algorithm wants to take advantage of a potential > speed-up, it may decide to check whether hasConsume!Range, and > call consumeFront instead of front + popFront. Nobody is obliged > to do so. > > The only problem I can see is potential code duplication, which > is lower priority in performance-critical part of code, IMO. > You can do that in your own code if you think it is critical. Code-duplicating phobos and other libraries to make everyone take advantage of that may be a good intent, but I am afraid the cost in code maintenance may overcomes the performance gain. I am not sure what would be worse, in the long run, between asking developpers to make front remain valid after popFront until next call to front, or having two different standard ways to iterate an input range (front/popFront and consumeFront).
Re: Proposal: takeFront and takeBack
If you really don't need the value, you could devise a "justPop" method that does not return (by the way, overloading by return type would be an amazing feature here). The idea is not "we should return a value everytime we pop", but "we should pop when we return a value". -- Christophe
Re: Proposal: takeFront and takeBack
"monarch_dodra" , dans le message (digitalmars.D:171175), a écrit : > For those few algorithms that work on bidirRange, we'd need a > garantee that they don't ever front/back the same item twice. We > *could* achieve this by defining a bidirectionalInputRange class > of range. filter does that. If you want to call front only oncem you have to cache the results or... pop as you take the front value. popFrontN and drop will crash too.
Re: Let's stop parser Hell
deadalnix , dans le message (digitalmars.D:171330), a écrit : > D isn't 100% CFG. But it is close. What makes D fail to be a CFG?
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171685), a écrit : > I mean, is it possible to have the original code work? > > auto bar = foo.chain("bar"); > > Or perhaps more appropriate: > > auto bar = foo.append("bar"); What is wrong with foo.chain(["bar"])? If you do not want the heap allocation of the array, you can create a one-element range to feed to chain (maybe such a thing could be placed in phobos, next to takeOne). struct OneElementRange(E) { E elem; bool passed; @property ref E front() { return elem; } void popFront() { passed = true; } @property bool empty() { return passed; } @property size_t length() { return 1-passed; } //... } You can't expect chain to work the same way as run-time append. A compile-time append would be very inefficient if misused. > https://github.com/jacob-carlborg/dstep/blob/master/dstep/translator/Translator.d#L217 you might try this (untested) string function(Parameter) stringify = (x) { return (x.isConst? "const("~x.type~")": x.type) ~ (x.name.any?" "~translateIdentifier(x.name):""); } auto params = parameters .map!stringify() .chain(variadic? []: ["..."]) .joiner(", "); context ~= params; I am not sure this will be more efficient. joiner may be slowed down by the fact that it is called with a chain result, which is slower on front. But at leat you save yourself the heap-allocation of the params array*. I would use: context ~= parameters.map!stringify().joiner(", "); if (variadic) context ~= ", ..."; To make the best implementation would require to know how the String context works. *Note that here, stringify is not lazy, and thus allocates. It could be a chain or a joiner, but I'm not sure the result would really be more efficient.
Re: Why is std.algorithm so complicated to use?
Dmitry Olshansky , dans le message (digitalmars.D:171679), a écrit : > Because uniq work only on sorted ranges? Have you tried reading docs? > " > Iterates unique consecutive elements of the given range (functionality > akin to the uniq system utility). Equivalence of elements is assessed by > using the predicate pred, by default "a == b". If the given range is > bidirectional, uniq also yields a bidirectional range. > " Not, as the doc says, uniq work on any range, but remove only the consecutive elements. It you want to remove all duplicates, then you need a sorted range.
Re: Why is std.algorithm so complicated to use?
"Simen Kjaeraas" , dans le message (digitalmars.D:171678), a écrit : >> Well, I haven't been able to use a single function from std.algorithm >> without adding a lot of calls to "array" or "to!(string)". I think the >> things I'm trying to do seems trivial and quite common. I'm I overrating >> std.algorithm or does it not fit my needs? >> > > bearophile (who else? :p) has suggested the addition of eager and in-place > versions of some ranges, and I think he has a very good point. That would have been useful before UFSC. Now, writing .array() at the end of an algorithm call is not a pain. int[] = [1, 2, 2, 3].uniq().map!toString().array();
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171690), a écrit : >> int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; >> assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ][])); > > How should I know that from the example? Maybe there should be an example with an unsorted range, and a better explanation: | auto uniq(...) | Iterates unique consecutive elements of a given range (...) | Note that equivalent elements are kept if they are not consecutive. | | Example: | int[] arr = [ 1, 2, 2, 3, 4, 4, 4, 2, 4, 4]; | assert(equal(uniq(arr), [ 1, 2, 3, 4, 2, 4][]));
Re: Why is std.algorithm so complicated to use?
Andrei Alexandrescu , dans le message (digitalmars.D:171717), a écrit : > On 7/10/12 11:11 AM, Christophe Travert wrote: >> If you do not want the heap allocation of the array, you can create a >> one-element range to feed to chain (maybe such a thing could be placed >> in phobos, next to takeOne). >> >> struct OneElementRange(E) >> { >>E elem; >>bool passed; >>@property ref E front() { return elem; } >>void popFront() { passed = true; } >>@property bool empty() { return passed; } >>@property size_t length() { return 1-passed; } >>//... >> } > > Yah, probably we should add something like this: > > auto singletonRange(E)(E value) > { > return repeat(value).takeExactly(1); > } It would be much better to use: auto singletonRange(E)(E value) { return repeat(value).takeOne; } as well as: auto emptyRange(E)(E value) { return repeat(value).takeNone; } to have the advantages of takeOne and takeNone over takeExactly. > I don't think it would be considerably less efficient than a handwritten > specialization. But then I've been wrong before in assessing efficiency. Error message displaying the type of singletonRange(E) will be weird, but that's far from being the first place where it will be. Simplicity and maintainance of phobos seems more important to me. At least until these algorithm get stable, meaning open bug reports on algorithm and range are solved, and new bugs appears rarely. Optimisers should have no trouble inlining calls to Repeat's methods...
Re: Why is std.algorithm so complicated to use?
Andrei Alexandrescu , dans le message (digitalmars.D:171723), a écrit : >> auto emptyRange(E)(E value) >> { >>return repeat(value).takeNone; >> } > That also seems to answer Jonathan's quest about defining emptyRange. > Just use takeNone(R.init). err, that should be more like: auto singletonRange(E)() // with no parameters { return takeNone!type_of(repeat(E.init))(); } An emptyRange compatible with singletonRange should be called singletonRange and take no parameter, so that emptyRange name could be reserved to a real statically empty range (which is pretty easy to implement). -- Christophe
Re: Why is std.algorithm so complicated to use?
"Daniel Murphy" , dans le message (digitalmars.D:171720), a écrit : > Could it be extended to accept multiple values? (sort of like chain) > eg. > foreach(x; makeRange(23, 7, 1990)) // NO allocations! > { > > } > I would use this in a lot of places I currently jump through hoops to get a > static array without allocating. That's a good idea. IMHO, the real solution would be to make an easy way to create static arrays, and slice them when you want a range. -- Christophe I it were just me, array litterals would be static, and people should use .dup when they want a a surviving slice. Well, if it were just me, all function signature should tell when references to data escape the scope of the function, and all data would be allocated automatically where it should by the compiler.
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171725), a écrit : > On 2012-07-10 17:11, Christophe Travert wrote: > >> What is wrong with foo.chain(["bar"])? > > I think it conceptually wrong for what I want to do. I don't know if I > misunderstood ranges completely but I'm seeing them as an abstraction > over a collection. With most mutable collection you can add/append an > element. That may be the source of your problem. ranges are not collections. They do not own data. They just show data. You can't make them grow. You can only consume what you have already read.
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171739), a écrit : > On 2012-07-10 18:42, Daniel Murphy wrote: >> "Jacob Carlborg" wrote in message >> news:jthlpf$2pnb$1...@digitalmars.com... >>> >>> Can't "map" and "filter" return a random-access range if that's what they >>> receive? >>> >> map can, and does. > > It doesn't seem to: > > auto a = [3, 4].map!(x => x); > auto b = a.sort; > > Result in one of the original errors I started this thread with. here, map is random-access. But random access is not enough to call sort: you need to have assignable (well, swapable) elements in the range, if you want to be able to sort it. values accessed via a map are not always assignable, since they are the result of a function. It seems the map resulting from (x => x) is not assignable. This is debatable, but since (x => x) is just a stupid function to test. Otherwise, you could try the folowing: auto a = [3, 4].map!(ref int (ref int x) { return x; })(); a.sort;
Re: Why is std.algorithm so complicated to use?
"Daniel Murphy" , dans le message (digitalmars.D:171741), a écrit : > "Christophe Travert" wrote in message > news:jthmu8$2s5b$1...@digitalmars.com... >> "Daniel Murphy" , dans le message (digitalmars.D:171720), a écrit : >>> Could it be extended to accept multiple values? (sort of like chain) >>> eg. >>> foreach(x; makeRange(23, 7, 1990)) // NO allocations! >>> { >>> >>> } >>> I would use this in a lot of places I currently jump through hoops to get >>> a >>> static array without allocating. >> >> That's a good idea. IMHO, the real solution would be to make an easy way >> to create static arrays, and slice them when you want a range. > > It's not quite the same thing, static arrays are not ranges and once you > slice them you no longer have a value type, and might be referring to stack > allocated data. With... this thing, the length/progress is not encoded in > the type (making it rangeable) but the data _is_ contained in the type, > making it safe to pass around. The best of both worlds, in some situations. OK, I see. This goes against the principle that ranges are small and easy to copy arround, but it can be useful when you know what you are doing, or when the number of items is small. I don't like makeRange much. Would you have a better name? smallRange? rangeOf?
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171725), a écrit : >> To make the best implementation would require to know how the String >> context works. >> > String is a wrapper around str.array.Appender. Then, if the purpose is to make the code efficient, I would use the loop and append everything to the result without creating the params array, and even without creating the string p. Appender is made to append everything directly to it efficiently.
Re: Why is std.algorithm so complicated to use?
Jacob Carlborg , dans le message (digitalmars.D:171769), a écrit : > On 2012-07-10 20:04, Andrei Alexandrescu wrote: > >> Then store an array. "No one's put a gun to yer head." >> http://youtu.be/CB1Pij54gTw?t=2m29s > > That's what I'm doing. > And that's what you should do. Algorithm are not made to be stored in struct or class instance. You could use InputRange(E) and friends to do that, but that's often not optimal. Algorithm are here to do their job and output non-lazy result in the end.
Re: Let's stop parser Hell
Timon Gehr , dans le message (digitalmars.D:171814), a écrit : > On 07/11/2012 01:16 AM, deadalnix wrote: >> On 09/07/2012 10:14, Christophe Travert wrote: >>> deadalnix , dans le message (digitalmars.D:171330), a écrit : >>>> D isn't 100% CFG. But it is close. >>> >>> What makes D fail to be a CFG? >> >> type[something] <= something can be a type or an expression. >> typeid(somethning) <= same here >> identifier!(something) <= again > > 'something' is context-free: > > something ::= type | expression. Do you have to know if something is a type or an expression for a simple parsing? The langage would better not require this, otherwise simple parsing is not possible without looking at all forward references and imported files.
Re: Inherited const when you need to mutate
Andrei Alexandrescu , dans le message (digitalmars.D:171828), a écrit : > On 7/10/12 5:19 PM, H. S. Teoh wrote: > > There is value in immutable objects that has been well discussed, which > is incompatible with logical constness. We can change the language such > as: a given type X has the option to declare "I want logical const and > for that I'm giving up the possibility of creating immutable(X)". That > keeps things proper for everybody - immutable still has the strong > properties we know and love, and such types can actually use logical const. > > A number of refinements are as always possible. I think this is a good idea, but for classes, inheritance is an issue. Example: class A { int a; int compute() const pure { return a; } final int fun() const pure { a.compute; // optimised out by the compiler return a.compute; } } class B : A { @mutable int b; override int compute() const pure { if(!b) b = longComputation(a); // a += 1; // error, a is bitwise-const return b; // mutating and returning a mutable part at the // programmer's risk } } A.compute is bitwise const. However B.compute is logical const. A.fun is bitwise const, and can be optimised. But that is no longer true with a B instance. However, the compiler must be able to make those optimizations, otherwise all the power of const for any non final object is lost, because someone may derive a logical const class. This means the programmer is *responsible* for creating a logical const-behavior. This is a serious issue. Given the system-programming aspect of D, I would say the programmer should be allowed to do such a thing, taking the risk to have an undefined behavior. But with great caution. At least, it will be less dangerous than casting away const. Just providing a way to make it impossible to create an immutable instance of some classes would make it less dangerous to cast away constness. -- Christophe
Re: opApply not called for foeach(container)
"monarch_dodra" , dans le message (digitalmars.D:171868), a écrit : > I'm wondering if this is the correct behavior? In particular, > since foreach guarantees a call to opSlice(), so writing "arr[]" > *should* be redundant, yet the final behavior is different. > > That said, the "issue" *could* be fixed if the base class defines > opApply as: "return opSlice().opApply(dg)" (or more complex). > However: > a) The implementer of class has no obligation to do this, since > he has provided a perfectly valid range. > b) This would force implementers into more generic useless > boilerplate code. > > What are your thoughts? Which is the "correct" solution? Is it a > bug with foreach, or should the base struct/class provide an > opApply? I think foreach should never call opSlice. That's not in the online documentation (http://dlang.org/statement.html#ForeachStatement), unless I missed something. If you want to use foreach on a class with an opSlice, then yes, you should define opApply. Otherwise, the user have to call opSlice himself, which seems reasonable. That's how I understand the doc. -- Christophe
Re: opApply not called for foeach(container)
"monarch_dodra" , dans le message (digitalmars.D:171902), a écrit : > I just re-read the docs you linked to, and if that was my only > source, I'd reach the same conclusion as you. I think the reference spec for D should be the community driven and widely available website, not a commercial book. But that's not the issue here. > however, my "The D > Programming Language", states: > *12: Operator Overloading > **9: Overloading foreach > ***1: foreach with Iteration Primitives > "Last but not least, if the iterated object offers the slice > operator with no arguments lst[], __c is initialized with lst[] > instead of lst. This is in order to allow ?extracting? the > iteration means out of a container without requiring the > container to define the three iteration primitives." > > Another thing I find strange about the doc is: "If the foreach > range properties do not exist, the opApply method will be used > instead." This sounds backwards to me. Skipping the last paragraph, a reasonable interpretation would be that foreach try to use, in order of preference: - for each over array - opApply - the three range primitives (preferably four if we include save) - opSlice (iteration on the result of opSlice is determined by the same system). opApply should come first, since if someone defines opApply, he or she obviously wants to override the range primitive iteration. opApply and range primitives may be reached via an alias this. opSlice is called only if no way to iterate the struct/class is found. I would not complain if the fourth rule didn't exist, because it's not described in dlang.org, but it is a reasonable feature that have be taken from TDPL (but then it should be added in dlang.org). This way, if arr is a container that defines an opSlice, and that does not define an opApply, and does not define range primitives: foreach (a, arr) ... and foreach (a, arr[]) ... should be stricly equivalent. Since the first is translated into the second. Both work only if arr[] is iterable. I think you hit a bug. -- Christophe
Re: Congratulations to the D Team!
Andrei Alexandrescu , dans le message (digitalmars.D:171945), a écrit : > On 7/11/12 1:40 PM, Jakob Ovrum wrote: >> Some classes don't lend themselves to immutability. Let's take something >> obvious like a class object representing a dataset in a database. How is >> an immutable instance of such a class useful? > > This is a good point. It seems we're subjecting all classes to certain > limitations for the benefit of a subset of those classes. Does Object really need to implement opEquals/opHash/... ? This is what limits all classes that do not want to have the same signature for opEquals. The problem is not only in the constness of the argument, but also in its purity, safety, and throwability (although the last two can be worked arround easily). You can compare struct, all having different opEquals/opHash/ You can put them in AA too. You could do the same for classes. Use a templated system, and give the opportunity for the class to provide its own signature ? There may be code bloat. I mean, classes will suffer from the same code bloat as structs. Solutions can be found reduce this code bloat. Did I miss an issue that makes it mandatory for Object to implement opEquals and friends ? -- Christophe
Re: Congratulations to the D Team!
"David Piepgrass" , dans le message (digitalmars.D:172007), a écrit : > @mutating class B : A > { > private int _x2; > public @property override x() { return _x2++; } > } A fun() pure; You can't cast the result of fun to immutable, because it may be a B instance.
Re: Inherited const when you need to mutate
"David Piepgrass" , dans le message (digitalmars.D:172009), a écrit : >> Now, I recognize and respect the benefits of transitive >> immutability: >> 1. safe multithreading >> 2. allowing compiler optimizations that are not possible in C++ >> 3. ability to store compile-time immutable literals in ROM >> >> (3) does indeed require mutable state to be stored separately, >> but it doesn't seem like a common use case (and there is a >> workaround), and I don't see how (1) and (2) are necessarily >> broken. > > I must be tired. > > Regarding (1), right after posting this I remembered the > difference between caching to a "global" hashtable and storing > the cached value directly within the object: the hashtable is > thread-local, but the object itself may be shared between > threads. So that's a pretty fundamental difference. > > Even so, if Cached!(...) puts mutable state directly in the > object, fast synchronization mechanisms could be used to ensure > that two threads don't step on each other, if they both compute > the cached value at the same time. If the cached value is > something simple like a hashcode, an atomic write should suffice. > And both threads should compute the same result so it doesn't > matter who wins. Yes. It is possible to write a library solution to compute a cached value by casting away const in a safe manner, even in a multithreaded environment. The limitation is that, if I'm not mistaken, this library solution cannot ensure it is not immutable (and potentially placed in ROM) when it is const, making the cast undefined.
Re: Congratulations to the D Team!
"Jonathan M Davis" , dans le message (digitalmars.D:172005), a écrit : > On Wednesday, July 11, 2012 13:46:17 Andrei Alexandrescu wrote: >> I don't think they should be pure. Do you have reasons to think otherwise? > > As I understand it, Walter's current plan is to require that opEquals, opCmp, > toString, and toHash be @safe const pure nothrow - for both classes and > structs. And is the plan add each tag one by one, breaking codes in many places each time ?
Re: All right, all right! Interim decision regarding qualified Object methods
Timon Gehr , dans le message (digitalmars.D:172014), a écrit : > Thank you for taking the time. > > Removing the default methods completely is actually a lot better than > making inheriting from Object optional or tweaking const beyond > recognition and/or usefulness. > I was afraid to suggest this because it breaks all code that assumes > that the methods are present in object (most code?), but I think it is > a great way to go forward. It's not worse thant breaking all code that overrides opEqual by changing it's signature. > Regarding toString, getting rid of it would imply that the default way > of creating a textual representation of an object would no longer be > part of Object, paving the way for the proposal that uses buffers and > scope delegates - this will be purely a library thing. I agree. toString should be a purely library solution. The standard library could easily use templates trying to use different ways to print the the object, depending on what methods are implemented for that object: direct conversion to string/wstring/dstring, a standard method using delegates, etc. > Regarding backwards-compatibility, an issue that is trivial to fix is > the invalidation of 'override' declarations in the child classes. > They can be allowed with the -d switch for those methods. And if they > use 'super', the compiler could magically provide the current default > implementations. Magic is not good for langage consistency. I would rather do a different fix: Introduce a class in the standard library that is like the current Object. To correct broken code, make all classes inheriting from Objet inherit from this new class, and rewrite opEqual/opCmp to take this new class as an argument instead of Object. This can be done automatically. People may not want to use that fix, but in that case, we don't have to implement a magical behavior with super. What can be used is deprecation: if I someone uses super.opEqual (meaning Object.opEqual), and others, he should bet a warning saying it's deprectated, with explanations on how to solve the issue. A possible course of action is this: - revert changes in Object (with renewed apologies to people having worked on that) - introduce a class implementing basic Hashes functions with the current signatures. (A class with the new signatures could be provided too, making use of the late work on Object, which would not be completely wasted after all) - introduce a deprecation warning on uses of Object.opEqual and friends, informing the programmer about the possibility to derive from the new class to solve the issue. - in the mean time, make the necessary changes to enable classes not to have those methods (like structs) - after the deprecation period, remove Object.opEqual and friends.
Re: All right, all right! Interim decision regarding qualified Object methods
"Mehrdad" , dans le message (digitalmars.D:172012), a écrit : > On Thursday, 12 July 2012 at 04:15:48 UTC, Andrei Alexandrescu > wrote: >> Required reading prior to this: http://goo.gl/eXpuX > > Referenced post (for context): >>> The problem is not only in the constness of the argument, but >>> also in > its purity, safety, and throwability (although the last two can be > worked arround easily). > > I think we're looking at the wrong problem here. > > If we're trying to escape problems with 'const' Objects by > removing the members form Object entirely, that should be raising > a red flag with const, not with Object. const has no problem. It is bitwise const, and it works like that. Logical const is not implemented in D, but that is a separate issue. The problem is to force people to use const, because bitwise const may not be suited for their problems. If opEquals and friends are const, then D forces people to use bitwise const, and that is the problem, that is largely widened by the fact that bitwise transitive const is particularly viral. But if we do not impose to implement any const methods, the problem disappear.
Re: just an idea (!! operator)
"Jonas Drewsen" , dans le message (digitalmars.D:172039), a écrit : > On Wednesday, 11 July 2012 at 11:18:21 UTC, akaz wrote: >> if needed, the operator !! (double exclamation mark) could be >> defined. >> >> ... > > Or the operator?? could be borrowed from c# > > auto a = foo ?? new Foo(); > > is short for: > > auto a = foo is null ? new Foo() : foo; or maybe: auto a = ! ! foo ? foo : new Foo(); || could be redifined to have this a behavior, but it would break code.
Re: just an idea (!! operator)
Christophe Travert, dans le message (digitalmars.D:172047), a écrit : > "Jonas Drewsen" , dans le message (digitalmars.D:172039), a écrit : >> On Wednesday, 11 July 2012 at 11:18:21 UTC, akaz wrote: >>> if needed, the operator !! (double exclamation mark) could be >>> defined. >>> >>> ... >> >> Or the operator?? could be borrowed from c# >> >> auto a = foo ?? new Foo(); >> >> is short for: >> >> auto a = foo is null ? new Foo() : foo; > > or maybe: > auto a = ! ! foo ? foo : new Foo(); I forgot to mention that foo would be evaluated only once (and the second operand would be evaluated lazily). This is the main point of this syntax, and it is not easily emulable (as long a lazy is not fixed).
Re: just an idea (!! operator)
Jacob Carlborg , dans le message (digitalmars.D:172056), a écrit : > On 2012-07-12 13:35, Jonas Drewsen wrote: > >> Or the operator?? could be borrowed from c# >> >> auto a = foo ?? new Foo(); >> >> is short for: >> >> auto a = foo is null ? new Foo() : foo; >> >> /Jonas >> > > I really like that operator. The existential operator, also known as the > Elvis operator :) . It's available in many languages with slightly > different semantics. > > -- > /Jacob Carlborg Sweet. | Elvis Operator (?: ) | | The "Elvis operator" is a shortening of Java's ternary operator. One | instance of where this is handy is for returning a 'sensible default' | value if an expression resolves to false or null. A simple example | might look like this: | | def displayName = user.name ? user.name : "Anonymous" //traditional | ternary operator usage | | def displayName = user.name ?: "Anonymous" // more compact Elvis | operator - does same as above (taken from http://groovy.codehaus.org/Operators#Operators-ElvisOperator)
Re: Making uniform function call syntax more complete a feature
"Thiez" , dans le message (digitalmars.D:172060), a écrit : >>> Have you considered adding operator overloading using UFCS >>> while you're at it? >> >> I assumed it's already possible to add operators >> non-intrusively, because operators are just syntactic sugar for >> method calls: >> >> ++var; // actual code >> var.opUnary!"++"(); // lowered once >> opUnary!"++"(var); // lowered twice (if necessary) >> >> If you're talking about overloading existing operators (which >> have been implemented as member functions) non-intrusively for >> other types, then I don't know, doesn't it work? > > I actually tried those yesterday (with opEquals and opCmp on > structs) and couldn't get it to work. Code still used what > appeared to be an automatically generated opEquals (that appears > to perform a bitwise comparison) instead of my UFCS opEquals. This behavior for opEquals is debatable, but make sense. If the designer of a struct did not implement opEquals, it may be that he intended opEqual to be the default opEqual. If you overload opEquals for such struct, you may be hijacking it's intended behavior: your not just adding a functionality, your overriding an existing functionality. Did you try operators that are not automatically generated ? > It's already quite obvious that the compiler does not obey its > own rewrite rules (see > http://dlang.org/operatoroverloading.html#compare) Consider opCmp: > a < b > is rewritten to > a.opCmp(b) < 0 > or > b.opCmp(a) > 0 > Let's assume the first rule is always chosen. According to the > very rewrite rule we just applied, this must be rewritten to > a.opCmp(b).opCmp(0) < 0 > > It seems quite obvious the compiler does not rewrite compares on > integers or all hell would break loose... The language reference > should be more specific about these things. The rewrite rule obviously apply only if the comparison operator is not already defined for those types by the langage. That could be precised in the web site, but it's consistent. By the way, would it be possible to implement an opCmp that returns a double, to allow it to return a NaN ? That may allow to create values that are neither superior, nor inferior to other value, like NaNs. It's not possible to implement opCmp for a floating point comparison if opCmp is bound to return an int. Another reason to ban Object imposing a specific signature for opCmp in all classes...
Re: Counterproposal for extending static members and constructors
"Jonathan M Davis" , dans le message (digitalmars.D:172156), a écrit : > On Thursday, July 12, 2012 18:25:03 David Piepgrass wrote: >> I'm putting this in a separate thread from >> http://forum.dlang.org/thread/uufohvapbyceuaylo...@forum.dlang.org >> because my counterproposal brings up a new issue, which could be >> summarized as "Constructors Considered Harmful": >> >> http://d.puremagic.com/issues/show_bug.cgi?id=8381 > > I think that adding constructors to a type from an external source is > downright evil. It breaks encapsulation. I should be able to constrain > exactly > how you construct my type. If you want to create a free function (e.g. a > factory function) which uses my constructors, fine. But I'm completely > against > adding constructors externally. The proposal is not that add constructors. It is to create a free function (.make!Type(args)), that can called like a constructor, by writing Type(args). That does not break encapsulation.
Re: just an idea (!! operator)
"David Piepgrass" , dans le message (digitalmars.D:172164), a écrit : >>> Yeah, I've been planning to try and get this into D one day. >>> Probably >>> something like: >>> (a ?: b) -> (auto __tmp = a, __tmp ? __tmp : b) >> >> gcc used to have that extension and they dropped it... > > But GCC can't control the C++ language spec. Naturally there is a > reluctance to add nonstandard features. It's a successful feature > in C#, however, and a lot of people (including me) have also been > pestering the C# crew for "null dot" (for safely calling methods > on object references that might be null.) > > I don't see why you would use ?: instead of ??, though. Because ?: is the ternary conditionnal operator with missing second operand. a ?: b // or maybe a ? : b is just a short hand for a ? a : b (except a is evaluated only once).
Re: Counterproposal for extending static members and constructors
>> In any case, std.container already declares a make which encapsulates >> constructing an object without caring whether it's a struct or class (since >> some containers are one and some another), which I intend to move to >> std.typecons and make work with all types. That seems a lot more useful to me >> than trying to make a function act like a constructor when it's not - though >> I >> guess that as long as you imported std.typecons, I would just be providing >> the >> free function that your little constructor faking scheme needs. The same can be said for UFCS. Your just faking member functions with a free function. I don't understand why constructors are so different. A library might write generic code and use a constructor to perform something. I can't use that generic code if I don't own the struct or class and am able to write a constructor. You can argue that the library should have use make, instead of calling the constructor or using some cast. But they can't think of every usages, and may not know about make. Plus it may make the code uglier. It's like standard UFCS. library writer should never call a member function, and always call a free function that is templated, specialised to use the member function if available, to provide workarround, or that can be further specialised if someone wants to extend the class. yk...
Re: Move semantics for D
Benjamin Thaut , dans le message (digitalmars.D:172207), a écrit : > Move semantics in C++0x are quite nice for optimization purposes. > Thinking about it, it should be fairly easy to implement move semantics > in D as structs don't have identity. Therefor a move constructor would > not be required. You can already move value types for example within an > array just by plain moving the data of the value around. With a little > new keyword 'mov' or 'move' it would also be possible to move value > types into and out of functions, something like this: > > mov Range findNext(mov Range r) > { >//do stuff here > } > > With something like this it would not be neccessary to copy the range > twice during the call of this function, the compiler could just plain > copy the data and reinitialize the origin in case of the argument. > In case of the return value to only copying would be neccessary as the > data goes out of scope anyway. If Range is a Rvalue, it will be moved, not copied. It it's a Lvalue, your operation is dangerous, and does not bring you much more than using ref (it may be faster to copy the range than to take the reference, but that's an optimiser issue). auto ref seems to be the solution. > I for example have a range that iterates over a octree and thus needs to > internally track which nodes it already visited and which ones are still > left. This is done with a stack container. That needs to be copied > everytime the range is copied, which causes quite some overhead. I would share the tracking data between several instance of the range, making bitwise copy suitable. Tracking data would be duplicated only on call to save or opSlice(). You'd hit the issue of foreach not calling save when it should, but opSlice would solve this, and you could still overload opApply if you want to be sure. -- Christophe
Re: just an idea (!! operator)
"Jonas Drewsen" , dans le message (digitalmars.D:172242), a écrit : > Can you identify any ambiguity with an ?. operator. ? could be the begining of a ternary operator, and . the module scope indicator, or the beginning of a (badly) written float number. Both case can be disambiguated by the presence of the ':' in the case of a ternary operator. I don't think '?' has currently any other meaning in D.
Re: just an idea (!! operator)
"Roman D. Boiko" , dans le message (digitalmars.D:172259), a écrit : > On Friday, 13 July 2012 at 13:46:10 UTC, David Nadlinger wrote: >> I guess that this operator is only really worth it in languages >> where every type is nullable, though. >> >> David > > It might mean identity (return the argument unchanged) for value > types. It might mean: give me the default I provide as an extra argument: Example: car?.driver?.name ?: "anonymous"; rewrites: car? car.driver? car.driver.name? car.driver.name? car.driver.name :anonymous :anonymous :anonymous :anonymous
Re: nested class inheritance
"Era Scarecrow" , dans le message (digitalmars.D:172269), a écrit : > class Fruit { > int x; > class Seed { >void oneMoreToX() { > x++; //knows about Fruit.x, even if not instantiated >} > } > > static class Seed2 { >void oneMoreToX() { > // x++; //fails to compile, no knowledge of Fruit >} > } > } > > class Apple: Fruit { > class AppleSeed: Fruit.Seed { } //fails (no outer object > (Fruit.x) and makes no sense) > class AppleSeed2: Fruit.Seed2 { } //works fine > } AppleSeed does have a outer Fruit, and this Fruit happens to be an Apple. I don't see what is the issue, and what prevents the langage to support such AppleSeed. I'm not saying there is nothing that prevents the langage to support such pattern, I am not used to inner classes. Pleas enlighten me.
Re: nested class inheritance
"Era Scarecrow" , dans le message (digitalmars.D:172272), a écrit : > Then perhaps have the inherited class within fruit? > > class Fruit { >class Seed {} >class Appleseed : Seed {} > } But then AppleSeed doesn't know about Apple
Re: nested class inheritance
Andrei Alexandrescu , dans le message (digitalmars.D:172280), a écrit : >> For Fruit.Seed it's Fruit, for AppleSeed it's Apple. This makes sense >> because the Apple, which AppleSeed sees is the same object, which >> Fruit.Seed sees as it's base type Fruit. > > That would mean AppleSeed has two outer fields: a Fruit and an Apple. Only one. Apple. And when AppleSeed.super seed this Apple, it sees a fruit. AppleSeed a; assert(is(typeof(a.outer) == Apple)); assert(is(typeof(a.super) == Seed)); assert(is(typeof(a.super.outer) == Fruit)); //but: assert(a.outer is a.super.outer); If you can't figure out how can a.outer and a.super.outer have two different types, but be the same, think about covariant return.
Re: Making uniform function call syntax more complete a feature
"Simen Kjaeraas" , dans le message (digitalmars.D:172349), a écrit : > On Thu, 12 Jul 2012 16:31:34 +0200, Christophe Travert > wrote: > >> By the way, would it be possible to implement an opCmp that returns a >> double, to allow it to return a NaN ? That may allow to create values >> that are neither superior, nor inferior to other value, like NaNs. It's >> not possible to implement opCmp for a floating point comparison if opCmp >> is bound to return an int. > > Why don't you just test it? Not like it'd be many lines of code. > > Anyways, yes this works. Thanks. I don't always have a d compiler at hand when I read this newsgroup. Maybe I should just write myself a todo to make this kind of test back home rather than directly posting the idea.
Re: Array index slicing
"bearophile" , dans le message (digitalmars.D:172300), a écrit : > If enumerate() is well implemented it's one way to avoid that > problem (other solutions are possible), now 'c' gets sliced, so > it doesn't start from zero: > > import std.stdio; > void main() { > auto M = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; > foreach (r, row; M) > foreach (c, ref item; enumerate(row)[1 .. $]) > item = c * 10 + r; > writefln("%(%s\n%)", M); > } enumerate could be useful with retro too. You may want to change the order of the enumeration, but not the order of the indices.
Re: Why doesn't to!MyEnumType(42) work
"Era Scarecrow" , dans le message (digitalmars.D:172568), a écrit : > On Monday, 16 July 2012 at 21:59:17 UTC, Tommi wrote: >> On Monday, 16 July 2012 at 20:22:12 UTC, Era Scarecrow wrote: >>> MyEnumType y = cast(MyEnumType) 42; //Error: wtf is 42 anyways? >> >> Like the previous fellow said, it's not an error. > > I had the impression it was illegal by the compiler; Logically > forcing an enum to an invalid state is probably undefined and > unstable (but casting with compile-time constant that's provably > correct would be different). Also I've never tried force casting > the enums, so.. Hmmm... > > I suppose 'use at your own risk' applies here. For what it's worth, I think cast should be at your own risk, and to!MyEnumType should assert the enum is valid.
Re: Why is std.algorithm so complicated to use?
Jonathan M Davis , dans le message (digitalmars.D:172564), a écrit : > They're likely to contain a lot of stuff negation of other template > constraints. For instance, > > auto func(R)(R range) > if(isForwardRange!R && !isBidirectionalRange!R) > {} > > auto func(R)(R range) > if(isBidirectionalRange!R) > {} > > If you have a function with very many overloads, it can be very easy to end > up > with a bunch of different template constraints which are all slightly > different. > std.algorithm.find is a prime example of this. > > But as much as it may be a bit overwhelming to print every failed constraint, > without doing that, you _have_ to go to the source code to see what they > were, > which isn't all that great (especially if it's not in a library that you > wrote > and don't normally look at the source of - e.g. Phobos). > > On the other hand, a failed instantiation of std.conv.to would print out > reams > of failed constraints... The compiler could stop displaying at about 10 failed constrains and claim there are more. It would be best if it could figure out what are the 10 most interesting constrains, but that may not be easy! Then it's up to the programmer to use template constrains, static if and eventually pragma, to allow the compiler to display pleasant error messages. The langage could help you by allowing you to make hiearchized template constrains, but static if is a fine solution most of the time.
Re: Definition of "OutputRange" insuficient
"monarch_dodra" , dans le message (digitalmars.D:172586), a écrit : > I was trying to fix a few bugs in algorithm, as well as be more > correct in template type specifiers, and I have to say: There is > a serious restraint in the definition of an outputRange. > > The current definition of "isOutputRange" is simply that "an > output range is defined functionally as a range that supports the > operation put". > > The problem with this is this definition is not very useful (at > all). Not just because it lacks front/popFront (this is actually > OK). It is because it lacks an "empty". This makes it virtually > un-useable unless you are blind writing to an infinite output > range. > > The proof that it is useless is that NOT A SINGLE ALGORITHM is > compatible with output ranges. All algorithms that really only > require an "output" systematically actually require > "isForwardRange", or, worse yet "isInputRange" (!). This is only > because they all make use of range.empty. OutputRange is designed for infinite output ranges, like output files and appender. Copy is the only algorithm that uses OutputRange. But still, this is enough. That enables to do a lot of things, since copy can branch to any lazy input range performing all the generic operation you want*. However, output range with a limited capacity are not taken into account. They are partly covered by the input ranges family. Most ranges that are output ranges with a capacity are also input ranges. Arguably, we may want to cover output ranges with capacity that are not input range. This would may make fill cleaner. uninitializedFill would be fill with an output range that does not defines a front method, which would be much cleaner than using emplace arbitrarily on a range that is not designed for that. This also opens the door to an algorithm that copy a input range into an output range, but stops when the output range is full of the input range is empty, and return both filled output range and consumed input range (the input range could be taken by ref). Copy could be improved with an additional "StopPolicy" template argument. To do this, two methods could be added to output ranges, one telling if the range is full, and one telling what is the remaining capacity of the range (capacity already has a meaning, some other word should be used). These methods are like empty and length. -- Christophe Out of topic foot note: * After thoughts, one thing you can't do directly in phobos is to modify an output range to return a new output range. You are obliged to apply the operation on the input range. For example: input.map!toString().copy(output); can't be written: input.copy(output.preMap!toString()); Creating a modified output range like my (badly named) output.preMap can be useful, but may be to marginal to be put in Phobos. With a revamped stdio using more ranges, this may become less marginal. map, joiner, filter, chain, take and friends, zip and chunks may have a meaning for output range with capacity...
Re: Initialization of std.typecons.RefCounted objects
Matthias Walter , dans le message (digitalmars.D:172673), a écrit : > I looked at Bug #6153 (Array!(Array!int) failure) and found that the > > This exactly is what makes the following code fail: > > Array!(Array!int) array2d; > array2d.length = 1; > array2d[0].insert(1); > > The inner array "array2d[0]" was not initialized and hence the reference > pointer is null. Since Array.opIndex returns by value, the 'insert' > method is called on a temporary object and does not affect the inner > array (still being empty) which is stored in the outer array. > > What do you think about this? > > Must the user ensure that the Array container is always initialized > explicitly? If yes, how shall this happen since the only constructor > takes a (non-empty) tuple of new elements. Or shall opIndex return by > reference? I think opIndex should return by reference. opIndexAssign is of no help when the user want to use a function that takes a reference (here Array.insert). It is normal that Array uses default construction when someone increases the array's length. Besides that point, I don't see why default-constructed Array have an uninitialised Payload. This makes uninitialised Array behaves unexpectedly, because making a copy and using the copy will not affect the original, which is not the intended reference value behavior. Correcting default-initialization of Array would correct your bug, but would not correct the wrong behavior of Array when the value returned by opIndex is used with a non-const method with other objects (dynamic arrays?). So both have to be changed in my opinion. -- Christophe
Re: Initialization of std.typecons.RefCounted objects
I see you found the appropriate entry to discuss this bug: http://d.puremagic.com/issues/show_bug.cgi?id=6153
Re: Octal Literals
"Dave X." , dans le message (digitalmars.D:172680), a écrit : > Not that this really matters, but out of curiosity, how does this > template work? By looking at the sources, if the template argument is a string, the program just compute the octal value as a human would do, that is it makes the sum of the digits multiplied by their respective power of 8. If the template argument is not a string, it is transformed into a string and the previous algorithm is used. -- Christophe
Re: Formal request to remove "put(OutRange, RangeOfElements)"
That sounds reasonable and justified. Let's wait to know if people maintaining legacy code will not strongly oppose to this.
Re: Initialization of std.typecons.RefCounted objects
"monarch_dodra" , dans le message (digitalmars.D:172700), a écrit : > I think it would be better to "initialize on copy", rather than > default initialize. There are too many cases an empty array is > created, then initialized on the next line, or passed to > something else that does the initialization proper. Not default-initializing Array has a cost for every legitimate use of an Array. I think people use Array more often than they create uninitialized ones that are not going to be used before an other Array instance is assigned to them, so Array would be more efficient if it was default initialized and never check it is initialized again. But that's just speculation. > You'd get the correct behavior, and everything else (except dupe) > works fine anyways. Keeping the adress of the content secret may be a valuable intention, but as long as properties and opIndex does not allow to correctly forward methods, this is completely broken. Is there even a begining of a plan to implement this? I don't see how properties or opIndex could safely forward methods that uses references and that we do not control without escaping the reference to them. That's not possible until D has a complete control of escaping references, which is not planned in the near or distant future. Not to mention that having a complete control of escaping reference break lots of code anyway, and might considerably decrease the need for ref counted utilities like... Array. Please, at least give me hope that there is light at the end of the tunnel. -- Christophe
Re: Initialization of std.typecons.RefCounted objects
"monarch_dodra" , dans le message (digitalmars.D:172710), a écrit : > One of the reason the implementation doesn't let you escape a > reference is that that reference may become (_unverifiably_) > invalid. The same applies to a dynamic array: it is undistinguishable from a sliced static array. More generally, as long as you allow variables on the stack with no escaped reference tracking, you can't ensure references remain valid. Even in safe code. If I want my references to remain valid, I use dynamic array and garbage collection. If I use Array, I accept that my references may die. Array that protects the validity of their references are awesome. But, IMHO, not at that cost. > ...That said, I see no reason for the other containers (SList, > I'm looking at you), not to expose references. I'm against not exposing reference, but all containers will be implemented with custom allocator someday. > The current work around? Copy-Extract, manipulate, re-insert. > Sucks. IMO, what sucks even more is that arr[0].insert(foo) compiles while it has no effect. arr[0] is a R-value, but applying method to R-value is allowed. I don't know the state of debates about forbiding to call non-const methods on R-values. I think this would break too much code.