www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - ` safe` by default. What about ` pure` and `immutable` by default?

reply Mike Franklin <slavo5150 yahoo.com> writes:
I think I may have found a simple migration path to  safe by 
default.  I'm still thinking it through, but if I can justify it, 
I will write a DIP.

` safe` by default is a no-brainer in my opinion, but `pure` and 
`immutable` by default are less obvious.  With the aforementioned 
potential DIP, I have an opportunity to correct purity and 
mutability defaults as well...but is that something we want to do?

Can anyone save me some trouble and articulate why it would be 
bad to have `pure` and/or `immutable` by default?

Thanks,
Mike
Apr 15
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 16/04/2019 3:59 PM, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by default.  
 I'm still thinking it through, but if I can justify it, I will write a DIP.
Can you please give us a quick overview of what you're thinking of? Its a subject that has been thought about in the past with not much success.
Apr 15
parent Mike Franklin <slavo5150 yahoo.com> writes:
On Tuesday, 16 April 2019 at 04:20:56 UTC, rikki cattermole wrote:

 Can you please give us a quick overview of what you're thinking 
 of? Its a subject that has been thought about in the past with 
 not much success.
In order for me to articulate it correctly and completely, I'd have to basically write the DIP in this forum thread. I'm not prepared to do that at this time. As I said, I still need to think it through, even for myself. I'd prefer to leave that discussion for the DIP review. At the moment, I just need to know if, for the benefit of D in the long term, we want `pure` and/or `immutable` by default so I can clarify the scope of the DIP. Thanks, Mike
Apr 15
prev sibling next sibling parent reply Eugene Wissner <belka caraus.de> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?

 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?

 Thanks,
 Mike
immutable by default would be good if D wasn't a systems programming language. Immutability plays nice with a GC, but even with a GC it probably requires a different one which is more efficient with immutable data and I'm not sure we can have one, because we have still to support pointers, pointer arithmetic and similar stuff that can break garbage collection. On the other hand what Phobos/D assumes that some things are mutable. What happens if I say: auto range = map!(a => a )([1, 2, 3]); Will it return an immutable range I can't work with? Immutability shouldn't be default at least as long as there isn't something like __mutable or _metadata or head const for custom types. As for pure, I think it would be better to just remove it instead of making it default. A year ago, after my first years with a lot of D, I was totally crazy about pure. But after that time I began to write again some JS and PHP and wondered first "How can I write programs without pure?". And now I would say it isn't bad at all without it. But if there is no way around pure, I would say yes, pure by default would be reasonable even if I would prefer " pure(false)/ pure(true)".
Apr 15
parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 16 April 2019 at 05:44:22 UTC, Eugene Wissner wrote:
 I began to write again some JS and PHP and wondered first "How 
 can I write programs without pure?". And now I would say it 
 isn't bad at all without it.
Yeah, pure definitely isn't a must-have, but I also think it is kinda nice... as long as the little leaf functions are easy, and IMO that is where the default shines. My example is a property getter: pure nothrow safe const nogc int a() { return a_; } When the attribute list is longer than the function, I just think that is silly.
 But if there is no way around pure, I would say yes, pure by 
 default would be reasonable even if I would prefer 
 " pure(false)/ pure(true)".
So a point I have thought of recently: we also need `null` in addition to true and false there. This acts as if the attribute was never given. Why? safe: /* snip a lot of stuff */ T map(alias fn, T)(T[]) {} That map template right now is considered safe, because of the colon above. This means it MUST also call a safe fn. But templates are usually more flexible; it will infer the safety based on the `fn` sent to it. With safe, we can turn it off with its opposites... system T map.... (or safe(false) same thing) But this means it can no longer be called by safe functions! Still not the same result as just leaving the attribute off. Hence my proposal: safe(null) T map... The null there just erases any matching attribute that was inherited from a group above, allowing the compiler to revert to the default - which for templates, is actually inference.
Apr 16
prev sibling next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.
+1
 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?
I honestly think that data should be implicitly mutable. Data is naturally mutable and it should be left upon the user to decide about immutability. Look at rust, it has `immutable` data by default but it offers a mechanism of rebinding immutable variables, so you may have something like this: let x = 5; ..... 500 lines of code later ..... println!("{}", x); // 5 or something else? You know that x is `immutable` but in those 500 lines of code you could always rebind x to another variable by doing `let x = 10`. This, in opinion, breaks the spirit of immutability where you know that once you see a variable initialized it will never be changed (in safe code). As for `pure`, as long as we don't have the equivalent of `not pure`, I don't see how it can be the default
 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?

 Thanks,
 Mike
Cheers, RazvanN
Apr 16
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 4/16/19 3:42 AM, RazvanN wrote:

 As for `pure`, as long as we don't have the equivalent of `not pure`, I 
 don't see how it can be the default
Same arguments for immutable by default -- we don't have something that makes immutable data mutable. I think safe by default is a good idea. A fabulous idea. Please do it. -Steve
Apr 16
prev sibling next sibling parent XavierAP <n3minis-git yahoo.es> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?
Immutable by default may be ok for Haskell (I haven't tried), but sounds terrible for D, or most of the perfectly good programs anyone may have written or will write in the future in any language.
Apr 16
prev sibling next sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?
Among other things immutable by default needs shadowing to reduce namespace pollution. Pure is good for some leaf functions, but is not workable in general case, also needs usual haskell-style purity infrastructure like IO monad, and likely succinct currying and lazy evaluation. Imagine refactoring if something deep suddenly needs to do IO - the usual problem for pure languages.
Apr 16
prev sibling next sibling parent sarn <sarn theartofmachinery.com> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?
One downside for functions in libraries: authors are more likely to break ABIs by switching an exported function from pure to impure if pure is the default (as opposed to the current opt-in). https://theartofmachinery.com/2016/10/05/function_attributes_and_d_abi.html By the way, your DIP would need a spec for un-applying attributes like pure. That functionality would fill a big gap by itself.
Apr 16
prev sibling next sibling parent Atila Neves <atila.neves gmail.com> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?

 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?

 Thanks,
 Mike
I think that ` safe` and `pure` should be the default, but not `immutable`. It's too restrictive. I'd make it `const` by default. I'm not *too* bothere about it though, because I have to write something before the variable's name to declare it anyway, so I myself just default to writing `const` instead of `auto`. The exception in nearly every case that isn't dealing with a dependency that isn't const-correct is ranges. As mentioned elsewhere on this thread, for `pure` to be the default we need to be able to negate it.
Apr 16
prev sibling next sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.
I think pure should be too (and actually, even nothrow maybe), but not immutable. With pure, you won't even actually notice it with the majority of functions... and if you do notice it, you can mostly just change a global to a function argument and solve it, which tends to be cleaner design anyway. Though, one surprising problem with pure is that floating point functions don't count! We need to think up some solution to that. immutable though is really a completely different paradigm. It is very invasive, there's no escape hatch (like with nothrow, you can put try/catch on to limit its influence, immutable has no way to get out), and I'd be surprised if any existing D code would work with that change; you'd definitely notice it. We brag about having pure and mutable together, I like having that.
Apr 16
prev sibling next sibling parent Meta <jared771 gmail.com> writes:
On Tuesday, 16 April 2019 at 03:59:38 UTC, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?

 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?

 Thanks,
 Mike
I think pure by default is a no-brainer as well. It's not nearly as restrictive in D to mark your function as pure as it is to have a pure function in other languages, due to weak purity - that seems to be a wholly unique invention on D's part and is just waiting for an academic paper or two to be written on the subject. All pure restricts you from doing is accessing global variables in a function scope. Ironically, PHP got this right; you need to declare inside the function which global variables you are accessing to use them (I think this might be the case in python as well, but may be misremembering). You can still throw Errors/Exceptions and modify the arguments passed to the function, and if those arguments are implicitly convertible to immutable, you get all the nice properties of a strongly-pure function. I can't see a downside, OTOH, other than needing pure(false) or impure or something to turn it off when needed.
Apr 16
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, April 15, 2019 9:59:38 PM MDT Mike Franklin via Digitalmars-d 
wrote:
 I think I may have found a simple migration path to  safe by
 default.  I'm still thinking it through, but if I can justify it,
 I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure` and
 `immutable` by default are less obvious.  With the aforementioned
 potential DIP, I have an opportunity to correct purity and
 mutability defaults as well...but is that something we want to do?

 Can anyone save me some trouble and articulate why it would be
 bad to have `pure` and/or `immutable` by default?
pure can be nice when it works, but really basic things like I/O don't work if pure is used. You can get around that for debugging with debug blocks, but if you have a bunch of code, and it turns out that you need to do something in it that isn't pure, you'd be screwed unless you go and mark a ton of code with impure (or whatever the opposite of pure would be). It's not like you can just opt-out in the middle like you can with safe by using trusted to use system code. Similarly, you can't opt-out of immutable in the middle of a complex type. It's transitive. So, having immutable be the default would cause a lot of problems in aggregate types. Also, immutable is very much at odds with basic D concepts like ranges. With how they're currently designed, they require mutability. If immutable were the default, you'd have to slap mutable _everywhere_. immutable and pure both work great in very functional code, but they don't work anywhere near as well in imperative code. And they're particularly bad if you're doing low level stuff. They're the sort of thing that functional languages force on you. D is multiparadigm. I would absolutely hate to have the language effectively try to force everything to be functional and make you opt-out all over the place. The fact that we have immutable and pure allow for useful things in the cases where they make a lot of sense, and you don't need mutability, and you don't need stuff like I/O. I strongly dispute the idea that your average program is written that way or that that's how most programmers are going to want to write their code. Walter and Andrei have talked before about how the attributes are supposed to be for larger programs where they bring enough benefit to be worth the trouble. And stuff like smaller scripts shouldn't need to worry about it. I don't think that there's any question that attributes like pure and immutable do not fit well in most smaller programs, and if they were the default, any basic scripts written in D would have to opt-out of them all over the place. safe has some of the same problems, but it's more flexible, and it's far more reasonable that most code be safe, since ultimately you want the top-level of all programs to be safe, and if the higher level constructs that are typically going to be used by smaller programs are safe, then making safe the default might be fine. So, I don't know that it's a bad idea to make safe the default (though Walter and Andrei have been opposed to it before, because they thought that it wasn't appropriate to require that smaller programs worry about it). But I do think that it would a terrible idea to make pure, immutable, or any other attribute that is not currently the default the default. IMHO, safe is the only that even might make sense. The various attributes are just too restrictive for it to make sense for them to be the default. - Jonathan M Davis
Apr 16
next sibling parent reply Eugene Wissner <belka caraus.de> writes:
On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote:
 pure can be nice when it works, but really basic things like 
 I/O don't work if pure is used. You can get around that for 
 debugging with debug blocks, but if you have a bunch of code, 
 and it turns out that you need to do something in it that isn't 
 pure, you'd be screwed unless you go and mark a ton of code 
 with impure (or whatever the opposite of pure would be). It's 
 not like you can just opt-out in the middle like you can with 
  safe by using  trusted to use  system code.
I/O doesn't work in pure code, because I/O isn't pure. And if you want an impure statement in pure code, you can just cast purity away same as pureMalloc does it. pure doesn't make any sense if it isn't default. Plenty of people for a valid reason don't care about the attributes. As soon as you have dependencies, you can't mark your own code as pure because you use some dependencies that may be 100% pure but aren't annotated as such. It is possible to write pure-annotated code only if you have not-invented-here-syndrom like me and have no dependencies. Pure should either be default or be completely removed, it is absolutely useless as it is today.
Apr 17
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, April 17, 2019 1:25:24 AM MDT Eugene Wissner via Digitalmars-d 
wrote:
 On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote:
 pure can be nice when it works, but really basic things like
 I/O don't work if pure is used. You can get around that for
 debugging with debug blocks, but if you have a bunch of code,
 and it turns out that you need to do something in it that isn't
 pure, you'd be screwed unless you go and mark a ton of code
 with impure (or whatever the opposite of pure would be). It's
 not like you can just opt-out in the middle like you can with
  safe by using  trusted to use  system code.
I/O doesn't work in pure code, because I/O isn't pure. And if you want an impure statement in pure code, you can just cast purity away same as pureMalloc does it.
Of course, I/O isn't pure, and casting with pure is almost always the wrong thing to do. It's like const. If you cast it away and mutate the value, then the const is a lie, and the assumptions that the compiler makes are wrong, which could result in wrong code. In the case of pure and I/O, casting could easily mean that I/O isn't done which the code expects to be done, because the compiler decided that a function didn't need to be called multiple times, because it was pure, and the result would be the same. Similarly, if the compiler is lied to about pure, that can really screw with immutable, because the compiler is able to make assumptions based on the fact that the code is pure and determine that some data has to be unique, because it could not have possibly be passed into the function and thus had to have been allocated within the function. pureMalloc is one case where it's arguably okay to cast with regards to pure - but there's been a lot of discussion about that, because it's incredibly easy to screw up the compiler guarantees in the process. It works with the GC, because of the extra control that the compiler has and the fact that programmer isn't the one that has to deal with freeing the memory. In general, if code is casting with regards to pure, it's almost certainly wrong. Exceptions to that exist, but they're extremely rare, and they must be done _very_ carefully to avoid running afoul of compiler guarantees.
 pure doesn't make any sense if it isn't default. Plenty of people
 for a valid reason don't care about the attributes. As soon as
 you have dependencies, you can't mark your own code as pure
 because you use some dependencies that may be 100% pure but
 aren't annotated as such. It  is possible to write pure-annotated
 code only if you have not-invented-here-syndrom like me and have
 no dependencies.

 Pure should either be default or be completely removed, it is
 absolutely useless as it is today.
If pure were the default, then you would have to turn it off on main on pretty much every program ever written. That should tell you something. The only programs which could avoid having main be pure would be those whose only input is the arguments to main and whose only output was the return value from main or a thrown exception that escapes main. And that's _very_ few programs. For code to work as pure, it needs to be written with that in mind and then cannot add stuff like I/O or caching later (caching could be added in _some_ cases when the cache is within a variable, but certainly, something like Phobos' memoize wouldn't work). To try and force a program in general to be pure is to be playing the same insane game that languages like Haskell play. Sure, D's pure isn't quite the same thing (it really should be noglobal at this point, not pure), but the effect at the call site is the same, and many of the same restrictions within a function still exist even if they're more relaxed. I'll grant you that it can be annoying to use pure when a library you depend on doesn't use it properly, but by forcing it everywhere, the net result will be that code all over the place will have to be marked with impure (or global) in order to work properly. And it's likely to be very common that you'd then have to go through large portions of code and add global all over the place in order to add a piece of functionality that you need that relates to I/O or caching or some other use case where you need to interact with mutable data that isn't passed in as an argument. pure works well when it is in smaller sections of code which were specifically written to be pure, IMHO, it's a disaster if you try to mark your entire program that way. It's just too easy to run into situations where you can't have a piece of code be pure. It's a problem similar to requiring const or immutable everywhere, only instead of restricting itself to specific pieces of data, it invades the entire call stack. It does work in some programs that are specifically written that way, but for a lot of programs it won't - especially programs that are not written in a functional manner. Trying to force an attribute like const, immutable, or pure as the default is effectively trying to force D code to be written in a functional manner instead of treating it as fully multiparadigm, and it's forcing a paradigm that is very much not in line with the languages that D grew from or with how D's standard libraries and common idioms currently work. D code is typically more functional in nature than other languages that stem from C/C++, but it's still very much an imperative, systems-level language. - Jonathan M Davis
Apr 18
parent reply Eugene Wissner <belka caraus.de> writes:
On Thursday, 18 April 2019 at 08:18:05 UTC, Jonathan M Davis 
wrote:
 On Wednesday, April 17, 2019 1:25:24 AM MDT Eugene Wissner via 
 Digitalmars-d wrote:
 On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis 
 wrote:
 pure can be nice when it works, but really basic things like 
 I/O don't work if pure is used. You can get around that for 
 debugging with debug blocks, but if you have a bunch of 
 code, and it turns out that you need to do something in it 
 that isn't pure, you'd be screwed unless you go and mark a 
 ton of code with impure (or whatever the opposite of pure 
 would be). It's not like you can just opt-out in the middle 
 like you can with  safe by using  trusted to use  system 
 code.
I/O doesn't work in pure code, because I/O isn't pure. And if you want an impure statement in pure code, you can just cast purity away same as pureMalloc does it.
Of course, I/O isn't pure, and casting with pure is almost always the wrong thing to do. It's like const. If you cast it away and mutate the value, then the const is a lie, and the assumptions that the compiler makes are wrong, which could result in wrong code. In the case of pure and I/O, casting could easily mean that I/O isn't done which the code expects to be done, because the compiler decided that a function didn't need to be called multiple times, because it was pure, and the result would be the same. Similarly, if the compiler is lied to about pure, that can really screw with immutable, because the compiler is able to make assumptions based on the fact that the code is pure and determine that some data has to be unique, because it could not have possibly be passed into the function and thus had to have been allocated within the function. pureMalloc is one case where it's arguably okay to cast with regards to pure - but there's been a lot of discussion about that, because it's incredibly easy to screw up the compiler guarantees in the process. It works with the GC, because of the extra control that the compiler has and the fact that programmer isn't the one that has to deal with freeing the memory. In general, if code is casting with regards to pure, it's almost certainly wrong. Exceptions to that exist, but they're extremely rare, and they must be done _very_ carefully to avoid running afoul of compiler guarantees.
 pure doesn't make any sense if it isn't default. Plenty of 
 people for a valid reason don't care about the attributes. As 
 soon as you have dependencies, you can't mark your own code as 
 pure because you use some dependencies that may be 100% pure 
 but aren't annotated as such. It  is possible to write 
 pure-annotated code only if you have not-invented-here-syndrom 
 like me and have no dependencies.

 Pure should either be default or be completely removed, it is 
 absolutely useless as it is today.
If pure were the default, then you would have to turn it off on main on pretty much every program ever written. That should tell you something. The only programs which could avoid having main be pure would be those whose only input is the arguments to main and whose only output was the return value from main or a thrown exception that escapes main. And that's _very_ few programs. For code to work as pure, it needs to be written with that in mind and then cannot add stuff like I/O or caching later (caching could be added in _some_ cases when the cache is within a variable, but certainly, something like Phobos' memoize wouldn't work). To try and force a program in general to be pure is to be playing the same insane game that languages like Haskell play. Sure, D's pure isn't quite the same thing (it really should be noglobal at this point, not pure), but the effect at the call site is the same, and many of the same restrictions within a function still exist even if they're more relaxed. I'll grant you that it can be annoying to use pure when a library you depend on doesn't use it properly, but by forcing it everywhere, the net result will be that code all over the place will have to be marked with impure (or global) in order to work properly. And it's likely to be very common that you'd then have to go through large portions of code and add global all over the place in order to add a piece of functionality that you need that relates to I/O or caching or some other use case where you need to interact with mutable data that isn't passed in as an argument. pure works well when it is in smaller sections of code which were specifically written to be pure, IMHO, it's a disaster if you try to mark your entire program that way. It's just too easy to run into situations where you can't have a piece of code be pure. It's a problem similar to requiring const or immutable everywhere, only instead of restricting itself to specific pieces of data, it invades the entire call stack. It does work in some programs that are specifically written that way, but for a lot of programs it won't - especially programs that are not written in a functional manner. Trying to force an attribute like const, immutable, or pure as the default is effectively trying to force D code to be written in a functional manner instead of treating it as fully multiparadigm, and it's forcing a paradigm that is very much not in line with the languages that D grew from or with how D's standard libraries and common idioms currently work. D code is typically more functional in nature than other languages that stem from C/C++, but it's still very much an imperative, systems-level language. - Jonathan M Davis
As it is now, pure is not like immutable or const, it is like safe, it is just an annotation that tells, whether the code does something you don't expect. Nobody knows if pure will mean one day more or something different, probably not, because even official libraries break purity. I always have to annotate the main function as impure. So what? It is still better to annotate a few functions as impure than the most code as pure. Casting away purity was meant by me only for debugging and similar things, not as permanent solution. Even Haskell provides perfomUnsafeIO for that. And if a pure function becomes one day impure, the code needs refactoring anyway. But I see, there is no way for someone who doesn't care about purity to disable it. Maybe compiler should provide some switch that disables purity, nogc and so forth for the code and all its dependencies. I actually agree with you that D is an imperative programming language and as I said above I think pure brings more harm than good and I could live without it at all. It is just there, it doesn't work as is and of course nobody knows, how it is supposed to work.
Apr 18
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, April 18, 2019 4:05:32 AM MDT Eugene Wissner via Digitalmars-d 
wrote:
 As it is now, pure is not like immutable or const, it is like
  safe, it is just an annotation that tells, whether the code does
 something you don't expect. Nobody knows if pure will mean one
 day more or something different, probably not, because even
 official libraries break purity.
pure means that the function does not access mutable, global state except via the function's arguments. That's it. nd the compiler _is_ able to do stuff with that and does so right now. It will elide function calls (though under such restricted circumstances that it's not really worth it), but the bigger gains come from how it helps the type system. For instance, Foo* foo(int i) pure { return new Foo(i); } immutable f = foo(); compiles just fine, because the compiler is able to determine that the return value of foo is unique and that therefore the cast is safe. That allows for far more complicated functions which can be used to create immutable objects without requiring any explicit casting. pure has been improved in a number of small ways like that over time as people have figured out how to make the compiler better leverage the guarantees that pure provides. If what you're looking for is to be able to elide function calls all over the place, then I doubt that you'll ever get that - if nothing else, because that would require more code flow analysis than Walter is typically willing to allow. Something like func(42) * func(42) will result in a call being elided if func is pure, but even splitting it up onto two lines kills that. e.g. auto f = func(42); f = func(42) * f; won't elide anything and likely never will. As such, the gains come from pure tend to be very localized, and slapping it on everything is going to tend to be of minimal benefit. It does ensure that such code doesn't access non-immutable globals if they're not passed to it, which might be nice to know, but how useful it is is debatable, and it makes it a royal pain if you need that code to access globals later for caching or I/O or whatever.
 I always have to annotate the main function as impure. So what?
 It is still better to annotate a few functions as impure than the
 most code as pure.
Except that most programs aren't written in a way that it makes any sense for the majority of their code to be marked as pure. I/O alone tends to kill that.
 Casting away purity was meant by me only for debugging and
 similar things, not as permanent solution. Even Haskell provides
 perfomUnsafeIO for that. And if a pure function becomes one day
 impure, the code needs refactoring anyway. But I see, there is no
 way for someone who doesn't care about purity to disable it.
 Maybe compiler should provide some switch that disables purity,
 nogc and so forth for the code and all its dependencies.

 I actually agree with you that D is an imperative programming
 language and as I said above I think pure brings more harm than
 good and I could live without it at all. It is just there, it
 doesn't work as is and of course nobody knows, how it is supposed
 to work.
I don't get this. pure is fairly well understood. The guarantees that it provides are well understood. The only thing that's really changed over time (other than the introduction of "weak" purity, which is what made it so that pure is really just noglobal rather than having anything to do with functional purity) is the set of things that the compiler is able to do based on the fact that a function is marked with pure. That list keeps getting longer (if slowly). It started with function call elision (based on "strong" purity, which is what pure had been originally) and has grown over time. As far as writing code goes, the hard part is when you try to write code that the compiler doesn't think is pure, but you're trying to write it in a way that it maintains the guarantees that the compiler makes with pure and bases its assumptions off of. _That_ can be hard to do, because it means fully understanding what the compiler will do based on pure, and that list grows over time. So, it's almost never something that really makes sense. But if you're just using pure without trying to do any casts, just letting the compiler determine what's pure and isn't (either by using templates or by having it yell at you when you mark something that's pure that isn't), it's very straightforward. It works exactly as intended, and plenty of people know how it's supposed to work. The only real problem with it is its name, because newcomers think that it's all about functional purity, which it really isn't. It's just providing a set of guarantees about a function that the compiler can build on, and one of the things that it can do is figure out that a function is functionally pure and then do stuff like elide calls - but it can do more than that (like implicitly casting to immutable under some circumstances), and that other stuff that it can do doesn't necessarily have much to do with functional purity, just the knowledge that the only arguments to the function are the ones passed to it. - Jonathan M Davis
Apr 18
parent reply Eugene Wissner <belka caraus.de> writes:
On Thursday, 18 April 2019 at 14:52:53 UTC, Jonathan M Davis 
wrote:
 pure means that the function does not access mutable, global 
 state except via the function's arguments. That's it. nd the 
 compiler _is_ able to do stuff with that and does so right now.
That compiler actually does something with pure is new to me. So I was wrong here.
 It will elide function calls (though under such restricted 
 circumstances that it's not really worth it), but the bigger 
 gains come from how it helps the type system. For instance,

 Foo* foo(int i) pure { return new Foo(i); }

 immutable f = foo();
I'm not really convinced this pattern is useful, but fine there are may be different use cases, I'm not familiar with. But this doesn't even work under similar circumstances: int* foo() pure { return new int(5); } immutable f = foo(); gives: cannot use non-constant CTFE pointer in an initializer `&[5][0]`
 Something like func(42) * func(42) will result in a call being 
 elided if func is pure, but even splitting it up onto two lines 
 kills that. e.g.

 auto f = func(42);
 f = func(42) * f;
I've just tested it and if I don't miss something, no call elision is done. GDC eliminates actually the call if it has the source code, but GDC does it whether the function is pure or not. I also don't see how it may be possible. size_t foo() pure { return cast(size_t) new int(5); } It is a perfectly valid pure function, that doesn't depend on any global state, doesn't have arguments, without casting away the impurity, but it returns different values every time.
 I don't get this. pure is fairly well understood.
Thinking of the last discussion about pure, just before pureMalloc was introduced, I got a different feeling, but well, it kind of does, what the specification says. I also don't find Haskell's purity "insane", but actually very useful and solid, so I might be biased torwards "strong purity" or "no purity at all".
Apr 18
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Apr 18, 2019 at 06:33:33PM +0000, Eugene Wissner via Digitalmars-d
wrote:
[...]
 Thinking of the last discussion about pure, just before pureMalloc was
 introduced, I got a different feeling, but well, it kind of does, what
 the specification says.
 
 I also don't find Haskell's purity "insane", but actually very useful
 and solid, so I might be biased torwards "strong purity" or "no purity
 at all".
[...] It may help to understand the historical context in which D's purity, particularly "weak purity", arose. What D calls "strong purity" is equivalent to Haskell's purity, and was the original definition of 'pure' in D. What we call today "weak purity" came as a relaxation of strong purity in order to increase the scope of 'pure's applicability, and by so doing, increase the amount of code that can be made strongly pure. More details in this article: http://klickverbot.at/blog/2012/05/purity-in-d/ T -- Never trust an operating system you don't have source for! -- Martin Schulze
Apr 18
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, April 18, 2019 12:33:33 PM MDT Eugene Wissner via Digitalmars-d 
wrote:
 On Thursday, 18 April 2019 at 14:52:53 UTC, Jonathan M Davis

 wrote:
 pure means that the function does not access mutable, global
 state except via the function's arguments. That's it. nd the
 compiler _is_ able to do stuff with that and does so right now.
That compiler actually does something with pure is new to me. So I was wrong here.
 It will elide function calls (though under such restricted
 circumstances that it's not really worth it), but the bigger
 gains come from how it helps the type system. For instance,

 Foo* foo(int i) pure { return new Foo(i); }

 immutable f = foo();
I'm not really convinced this pattern is useful, but fine there are may be different use cases, I'm not familiar with. But this doesn't even work under similar circumstances: int* foo() pure { return new int(5); } immutable f = foo(); gives: cannot use non-constant CTFE pointer in an initializer `&[5][0]`
I take it that you were trying to initialize a variable that has to be initialized during CTFE? CTFE doesn't particularly like casts, and it couldn't even have pointers transfer from compile-time to runtime until fairly recently. So, the code generation probably inserts a cast that CTFE doesn't like. It works at runtime, and it should probably be made to work at compile-time. It's also more useful with far more complex pieces of code - like if you were initializing an immutable AA with a bunch of values or some other piece of data that required more than a constructor. It can be done without pure, but that then requires a cast, and it's up to the programmer to make sure that the data is unique, and it's safe to cast it to immutable, whereas with pure, the compiler can verify that for you.
 Something like func(42) * func(42) will result in a call being
 elided if func is pure, but even splitting it up onto two lines
 kills that. e.g.

 auto f = func(42);
 f = func(42) * f;
I've just tested it and if I don't miss something, no call elision is done. GDC eliminates actually the call if it has the source code, but GDC does it whether the function is pure or not. I also don't see how it may be possible.
It's my understanding that calling a strongly pure function multiple times within the same expression will result in the compiler eliding more than the first call (though I haven't tested it recently). The function's parameters must therefore be immutable (or implicitly convertible to immutable as would be the case with ints), or it can't do it. So, _very_ few functions will qualify, and how often does anyone call the same function multiple times with the same arguments in a single expression? For it to really be useful, you'd have to be doing a lot of math code with pure functions or something else that involved a lot of immutable variables (which most code doesn't have) where it made sense to call the same function multiple times in the same expression (which most code doesn't do). It would be more useful with code flow analysis, because then you'd potentially get call elision across an entire function, but given Walter's stance on code flow analysis, I doubt that it's ever happening. In general, while it is my understanding that the compiler will elide multiple, identical calls to a strongly pure function within a single expression, that just isn't very useful in practice - which is part of why the whole idea that pure is there for actual, functional purity is kind of bogus. It's also why pure was expanded beyond strongly pure, because strongly pure functions don't happen often without weakly pure function helpers, and even then, they don't happen often. In reality, the biggest benefits to pure probably come from constructing immutable objects, with the secondary benefit being that you know that a section of code can't access any globals except through function arguments.
 size_t foo() pure
 {
      return cast(size_t) new int(5);
 }

 It is a perfectly valid pure function, that doesn't depend on any
 global state, doesn't have arguments, without casting away the
 impurity, but it returns different values every time.
Well, you found a loophole then. The fact that you can allocate in a pure function is extremely useful, but it does come with the caveat that even though the allocated objects will always have the same value, they won't be the exact same object. So, by casting to get the pointer value, you can indeed cheat it. I'm not sure how possible it is to have the compiler prevent it, but it's also not something that's likely to be a problem in practice. It _is_ a loophole though brought on by one of the aspects of pure that was made more lax in order to increase its usefulness.
 I don't get this. pure is fairly well understood.
Thinking of the last discussion about pure, just before pureMalloc was introduced, I got a different feeling, but well, it kind of does, what the specification says.
The problem with pureMalloc is that you're trying to emulate what happens when allocating memory via new, which technically violates not accessing mutable, global state. It's just that it was decided that they way that it did it with new was acceptable, since mutating the GC bookkeeping wasn't really part of the program's logical state (though that does lead to the loophole you mentioned above). pureMalloc is then having the programmer do something similar without the compiler's help and without the GC cleaning up after it (so, it needs a corresponding free call). Call elision in particular is deadly, and while it's not going to happen often, having it happen in a way that would result in memory being freed twice or not freed at all would be a big problem. So, the whole pureMalloc thing is a bit of a mess. Certainly, it's not dealing with pure in any kind of normal manner, and it's trying to convince the compiler that something can be considered pure without the compiler then having problems due to the fact that it isn't actually pure. As far as just using pure goes and what that does, it's well understood overall. It's trying to trick the compiler where things get messy.
 I also don't find Haskell's purity "insane", but actually very
 useful and solid, so I might be biased torwards "strong purity"
 or "no purity at all".
I programmed in Haskell a fair bit in college. I think that it was a good experience, because it greatly increased my ability to write functional code, and it greatly increased how good I was with stuff like recursion. That being said, I think that it's an insane way to program in practice (monads being a prime example of some of what happens when you go down that route). The fact that D is multiparadigm means that you can use such idioms where they make a lot of sense (e.g. a lot of range code tends to be fairly functional in nature), but it's not forced on you. I really don't understand anyone who would _want_ to program in Haskell (or any language like it) as much more than a learning experience. Regardless, D's pure really doesn't have much to do with functional purity at this point, even if that was why it was originally put in the language. Having it be noglobal would be far more accurate, though it _can_ be used to have actual, functional purity in some cases. It's useful to have, but if we were going to ditch one of the function attributes, I'd probably put it near the top of the list. I don't want to lose it, but it tends to be truly useful in a rather limited number of circumstances, and I would hate to see it forced on code in general. - Jonathan M Davis
Apr 18
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/18/19 2:33 PM, Eugene Wissner wrote:
 On Thursday, 18 April 2019 at 14:52:53 UTC, Jonathan M Davis wrote:
 pure means that the function does not access mutable, global state 
 except via the function's arguments. That's it. nd the compiler _is_ 
 able to do stuff with that and does so right now.
That compiler actually does something with pure is new to me. So I was wrong here.
There's a lively discussion at https://github.com/dlang/dlang.org/pull/2627. Specialists welcome.
Apr 18
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote:
 On Monday, April 15, 2019 9:59:38 PM MDT Mike Franklin via 
 Digitalmars-d wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?

 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?
if you have a bunch of code, and it turns out that you need to do something in it that isn't pure, you'd be screwed unless you go and mark a ton of code with impure (or whatever the opposite of pure would be). It's not like you can just opt-out in the middle like you can with safe by using trusted to use system code.
That's a good point that I almost always forget when we talk about making these attributes the default. To follow the safe/trusted/system model we'd need something like pure/almostPure/impure. Weak purity also might be able to provide some partial respite: int pure1(int n, ref IOWrapper io) pure { return pure2(n.to!string(), io); //The chain continues } int pure2(string s, ref IOWrapper io) pure { //Can't do this because writeln is impure //writeln("The value of s is ", s); io.writeln("The value of s is ", s); int result; //Do some other work return result; } struct IOWrapper { string[] writeQueue; void writeln(Args...)(Args args) pure { foreach (arg; args) writeQueue ~= args.to!string(); writeQueue ~= '\n'; } void writeAll() { foreach (msg; writeQueue) writeln(msg); } } And if you don't like that, you can instead accept IOWrappers by value and return them along with the result of your calculation in a Tuple. It's not pretty, but it works.
Apr 17
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, April 17, 2019 5:39:02 PM MDT Meta via Digitalmars-d wrote:
 On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote:
 On Monday, April 15, 2019 9:59:38 PM MDT Mike Franklin via

 Digitalmars-d wrote:
 I think I may have found a simple migration path to  safe by
 default.  I'm still thinking it through, but if I can justify
 it, I will write a DIP.

 ` safe` by default is a no-brainer in my opinion, but `pure`
 and `immutable` by default are less obvious.  With the
 aforementioned potential DIP, I have an opportunity to correct
 purity and mutability defaults as well...but is that something
 we want to do?

 Can anyone save me some trouble and articulate why it would be
 bad to have `pure` and/or `immutable` by default?
if you have a bunch of code, and it turns out that you need to do something in it that isn't pure, you'd be screwed unless you go and mark a ton of code with impure (or whatever the opposite of pure would be). It's not like you can just opt-out in the middle like you can with safe by using trusted to use system code.
That's a good point that I almost always forget when we talk about making these attributes the default. To follow the safe/trusted/system model we'd need something like pure/almostPure/impure.
pure is binary in nature, and backdoors with it are seriously problematic. It would be more accurate at this point to call pure noglobal, because the key thing is that a function is only able to access data through its function arguments. It can't access any kind of globals except through those function arguments, which means that basic stuff like I/O and caching tend to not work with it. The pure functional stuff that we can get in D then stems from the assumptions that the compiler is able to make based on what it can determine from the function arguments and the knowledge that all of the function's data comes from the function arguments. In the extreme case, that can allow for function call elision (though that's pretty rare), but more commonly, it allows for stuff like implicitly casting a function's return value to immutable when the compiler can determine that the memory for it has to be unique (which greatly simplifies constructing immutable objects). At a macro-level, all pure/ noglobal really does is make it so that when you look at a function signature, you know that it's not grabbing data from anywhere but the function arguments, and even that is frequently not very informative, because complex objects can do stuff like access global variables via stored pointers - something which reduces how informative the attribute is while not really making it easy to access globals when you actually need to. So, I'd honestly argue that having pure all over your entire code base would be far more detrimental than beneficial. It just doesn't provide great benefits at the macro level - mostly just within specific pieces of code that can actually take advantage of what the compiler can do based on pure/ noglobal. But ultimately, what the compiler needs to know when it does anything with pure/ noglobal is that there is no way that the function can access anything except via its arguments. All of its assumptions and optimizations stem from that. So, having _any_ kind of backdoor for that like you would with an trusted equivalent destroys the guarantee - just like having a backdoor for const that allows for mutating a const object would destroy the compiler's ability to know that const data hasn't changed and thus would make const pretty meaningless as far as compiler guarantees go. With safe, we could conceivably treat main as safe and then _require_ that any code that involves system then be verified by the programmer as safe and marked with trusted (it would be really annoying for anyone wanting to avoid caring about safe, but there's no technical reason why it's a problem - just the huge risk that programmers will start slapping trusted all over the place when they haven't actually verified the code but want the compiler to shut up so that they can get their work done). That isn't the same for pure at all. If main were marked with pure, then it musn't access global variables anywhere, and conceptually, running main multiple times with the same data would then always result in exactly the same result, because you can't pass out any other results via the function arguments to main and can't access anything not provided to main unless it was created within main (so, no I/O). There _are_ rare cases where a piece of code that isn't technically noglobal is actually able to follow the compiler's guarantees (e.g. std.datetime's LocalTime() is conceptually pure because it always returns exactly the same value every time it's called, but it has to create that value the first time that it's called, because it was determined unacceptable to have static constructors in Phobos - and that means casting to pure). But such functions are extremely rare and have to be done very carefully. And then there's the mess that's pureMalloc. There's been _tons_ of arguments over whether it's safe to have it, because there are real risks that it's going to be elided, and even people who are very knowledgeable about D have been having a hard time agreeing on what's going on there and what the compiler will or won't do. And all of that has to do with ensuring that something can be safely treated as pure when it isn't technically pure. It's _not_ something that your average programmer should even be considering doing. So, stuff like assumePure or an trusted version of pure would be incredibly risky.
 Weak purity also might be able to provide some partial respite:

 int pure1(int n, ref IOWrapper io) pure
 {
      return pure2(n.to!string(), io); //The chain continues
 }

 int pure2(string s, ref IOWrapper io) pure
 {
      //Can't do this because writeln is impure
      //writeln("The value of s is ", s);

      io.writeln("The value of s is ", s);

      int result;
      //Do some other work

      return result;
 }

 struct IOWrapper
 {
      string[] writeQueue;

      void writeln(Args...)(Args args) pure
      {
          foreach (arg; args)
              writeQueue ~= args.to!string();
          writeQueue ~= '\n';
      }

      void writeAll()
      {
          foreach (msg; writeQueue)
              writeln(msg);
      }
 }

 And if you don't like that, you can instead accept IOWrappers by
 value and return them along with the result of your calculation
 in a Tuple. It's not pretty, but it works.
You're basically getting into monads, which are a complex and difficult topic. It's how languages like Haskell are able to be pure while still having I/O. It's technically possible, but it's a royal pain, and most people end up having a very hard time understanding it. It's also not how much code is written unless programmers are forced to write that way. We do have something similar with output ranges, which allows for specific pieces of code to be written in a way that could involve I/O without explicitly involving I/O, but such code is still frequently not actually pure, because the output range will often output the data as it goes along rather than building a string to output at the end. Templates deal with that though, so the code using the output range is usually able be pure if the output range itself is pure. D's pure is a great tool in sections of code that are specifically written for it in mind, and we do have some language features and idioms that make it easier for code to be pure that might not be otherwise, but trying to make entire programs pure really doesn't make sense - not for what is supposed to be a multiparadigm language. It's just way too restrictive. So, improving the tools for it and doing a better job of making sure that stuff is pure when it can be would be useful, but IMHO, trying to make it the default would be far too dogmatic and a huge mistake. - Jonathan M Davis
Apr 18
parent reply Eugene Wissner <belka caraus.de> writes:
On Thursday, 18 April 2019 at 08:53:37 UTC, Jonathan M Davis 
wrote:
 There _are_ rare cases where a piece of code that isn't 
 technically  noglobal is actually able to follow the compiler's 
 guarantees (e.g. std.datetime's LocalTime() is conceptually 
 pure because it always returns exactly the same value every 
 time it's called, but it has to create that value the first 
 time that it's called, because it was determined unacceptable 
 to have static constructors in Phobos - and that means casting 
 to pure).
It isn't conceptually pure, it depends on the state of the word after compilation, it is just impure.
Apr 18
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, April 18, 2019 4:07:57 AM MDT Eugene Wissner via Digitalmars-d 
wrote:
 On Thursday, 18 April 2019 at 08:53:37 UTC, Jonathan M Davis

 wrote:
 There _are_ rare cases where a piece of code that isn't
 technically  noglobal is actually able to follow the compiler's
 guarantees (e.g. std.datetime's LocalTime() is conceptually
 pure because it always returns exactly the same value every
 time it's called, but it has to create that value the first
 time that it's called, because it was determined unacceptable
 to have static constructors in Phobos - and that means casting
 to pure).
It isn't conceptually pure, it depends on the state of the word after compilation, it is just impure.
I don't see why the word after compilation would matter one whit. The semantics are the same whether you use a static constructor to initialize the underlying variable or do it lazily as it's currently doing (it's just that lazy way requires a cast). It always returns exactly the same object every time. That's what matters. And actually, given how pure works in D, it could legally return a newly allocated object every time so long as its value were the same (that would defeat the purpose of the singleton, put it would be perfectly legal as far as D's pure goes and would even negate the need for a cast). If you're worried about absolute functional purity, D already threw that out the window when it allowed pure functions to do imperative stuff. Instead, D just cares about how the function behaves from the outside - LocalTime() behaves correctly with regard's to D's pure and it's guarantees. It only needs a cast because it does it lazily instead of using a static constructor like it did originally. - Jonathan M Davis
Apr 18
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/15/19 11:59 PM, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by default.  
 I'm still thinking it through, but if I can justify it, I will write a DIP.
 
 ` safe` by default is a no-brainer in my opinion, but `pure` and 
 `immutable` by default are less obvious.  With the aforementioned 
 potential DIP, I have an opportunity to correct purity and mutability 
 defaults as well...but is that something we want to do?
 
 Can anyone save me some trouble and articulate why it would be bad to 
 have `pure` and/or `immutable` by default?
 
 Thanks,
 Mike
I haven't participated much to this but here's a thought. This migration is again in the category "let's change things and evaluate the costs". It is so much better to go with "let's add and enjoy the benefits". It's cheap to make safe and pure the defaults for a given module. Plant this at the beginning: pure: safe: This is easy to enforce via scripting etc. The problem is there's no opting out of pure. (There's opting out of safe by using system or trusted.) So this is where the hammer should go: pure(false) We have discussed this on and off perhaps a couple dozen times. I've discussed it with Walter a few times and it is clear that some way of getting out of pure is sorely needed. This is what the DIP should be about.
Apr 19
next sibling parent reply Seb <seb wilzba.ch> writes:
On Friday, 19 April 2019 at 12:23:08 UTC, Andrei Alexandrescu 
wrote:
 On 4/15/19 11:59 PM, Mike Franklin wrote:
 I think I may have found a simple migration path to  safe by 
 default.  I'm still thinking it through, but if I can justify 
 it, I will write a DIP.
 
 ` safe` by default is a no-brainer in my opinion, but `pure` 
 and `immutable` by default are less obvious.  With the 
 aforementioned potential DIP, I have an opportunity to correct 
 purity and mutability defaults as well...but is that something 
 we want to do?
 
 Can anyone save me some trouble and articulate why it would be 
 bad to have `pure` and/or `immutable` by default?
 
 Thanks,
 Mike
I haven't participated much to this but here's a thought. This migration is again in the category "let's change things and evaluate the costs". It is so much better to go with "let's add and enjoy the benefits".
Most D code is yet to be written. Safe by default is sth. that all new D code should be
 It's cheap to make  safe and pure the defaults for a given 
 module. Plant this at the beginning:

 pure:  safe:
NOPE. That doesn't work as DMD doesn't set pure for member functions. --- pure: struct Foo { int bar() { return 42; } } void main() { Foo().bar(); // Error: pure function D main cannot call impure function onlineapp.Foo.bar } --- https://run.dlang.io/is/cgESWs
Apr 19
parent Eugene Wissner <belka caraus.de> writes:
On Friday, 19 April 2019 at 13:09:50 UTC, Seb wrote:
 On Friday, 19 April 2019 at 12:23:08 UTC, Andrei Alexandrescu 
 wrote:
 pure:  safe:
NOPE. That doesn't work as DMD doesn't set pure for member functions.
And it isn't good for the documentation.
Apr 19
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 19 April 2019 at 12:23:08 UTC, Andrei Alexandrescu 
wrote:
 (There's opting out of  safe by using  system or  trusted.)
This still doesn't work correctly for templates, which should be inferred based on input, not forced one way or another. We should be doing attr(true | false | null) three way! But yeah, that's where I would focus too. Then your modules can opt in pretty easily.
Apr 19
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Friday, April 19, 2019 7:52:13 AM MDT Adam D. Ruppe via Digitalmars-d 
wrote:
 On Friday, 19 April 2019 at 12:23:08 UTC, Andrei Alexandrescu

 wrote:
 (There's opting out of  safe by using  system or  trusted.)
This still doesn't work correctly for templates, which should be inferred based on input, not forced one way or another. We should be doing attr(true | false | null) three way! But yeah, that's where I would focus too. Then your modules can opt in pretty easily.
Definitely - though I'm not quite sure how allowing null would work with allowing expressions that result in bool, which is what we really should be doing rather than requiring an explicit true or false. It can obviously be done, but I'm not quite sure how that would be spec-ed out. - Jonathan M Davis
Apr 19
parent Mike Franklin <slavo5150 yahoo.com> writes:
On Friday, 19 April 2019 at 21:46:20 UTC, Jonathan M Davis wrote:

 We should be doing

 attr(true | false | null)

 three way!


 But yeah, that's where I would focus too. Then your modules 
 can opt in pretty easily.
Definitely - though I'm not quite sure how allowing null would work with allowing expressions that result in bool, which is what we really should be doing rather than requiring an explicit true or false. It can obviously be done, but I'm not quite sure how that would be spec-ed out.
I think we could make it an enum: enum AttributeState { yes, no, infer } Then the compiler will take `pure(MyCoolExpression)` and rewrite it to `pure(_d_setAttributeState(MyCoolExpression))`. In druntime we then implement an overloaded `AttributeState _d_setAttributeState(T)(T value)` to do the right thing. The spec can say we allow any expression that evaluates to `bool` or `typeof(null)` at compile-time. `_d_setAttributeState`, through D's metaprogramming and design-by-introspection abilities, can implement the spec to translate a `bool` or `typeof(null)` expression to an `AttributeState` value. Mike
Apr 19