digitalmars.D - ` safe` by default. What about ` pure` and `immutable` by default?
- Mike Franklin (11/11) Apr 15 2019 I think I may have found a simple migration path to @safe by
- rikki cattermole (3/5) Apr 15 2019 Can you please give us a quick overview of what you're thinking of? Its
- Mike Franklin (11/14) Apr 15 2019 In order for me to articulate it correctly and completely, I'd
- Eugene Wissner (23/35) Apr 15 2019 immutable by default would be good if D wasn't a systems
- Adam D. Ruppe (29/35) Apr 16 2019 Yeah, pure definitely isn't a must-have, but I also think it is
- RazvanN (22/34) Apr 16 2019 I honestly think that data should be implicitly mutable. Data is
- Steven Schveighoffer (5/7) Apr 16 2019 Same arguments for immutable by default -- we don't have something that
- XavierAP (5/7) Apr 16 2019 Immutable by default may be ok for Haskell (I haven't tried), but
- Kagamin (7/9) Apr 16 2019 Among other things immutable by default needs shadowing to reduce
- sarn (7/9) Apr 16 2019 One downside for functions in libraries: authors are more likely
- Atila Neves (10/22) Apr 16 2019 I think that `@safe` and `pure` should be the default, but not
- Adam D. Ruppe (16/18) Apr 16 2019 I think pure should be too (and actually, even nothrow maybe),
- Meta (17/29) Apr 16 2019 I think pure by default is a no-brainer as well. It's not nearly
- Jonathan M Davis (45/54) Apr 16 2019 pure can be nice when it works, but really basic things like I/O don't w...
- Eugene Wissner (13/21) Apr 17 2019 I/O doesn't work in pure code, because I/O isn't pure. And if you
- Jonathan M Davis (61/82) Apr 18 2019 Of course, I/O isn't pure, and casting with pure is almost always the wr...
- Eugene Wissner (22/122) Apr 18 2019 As it is now, pure is not like immutable or const, it is like
- Jonathan M Davis (61/81) Apr 18 2019 pure means that the function does not access mutable, global state excep...
- Eugene Wissner (31/45) Apr 18 2019 That compiler actually does something with pure is new to me. So
- H. S. Teoh (14/21) Apr 18 2019 [...]
- Jonathan M Davis (80/128) Apr 18 2019 I take it that you were trying to initialize a variable that has to be
- Andrei Alexandrescu (3/10) Apr 18 2019 There's a lively discussion at
- Meta (37/57) Apr 17 2019 That's a good point that I almost always forget when we talk
- Jonathan M Davis (83/142) Apr 18 2019 pure is binary in nature, and backdoors with it are seriously problemati...
- Eugene Wissner (4/12) Apr 18 2019 It isn't conceptually pure, it depends on the state of the word
- Jonathan M Davis (17/29) Apr 18 2019 I don't see why the word after compilation would matter one whit. The
- Andrei Alexandrescu (15/28) Apr 19 2019 I haven't participated much to this but here's a thought. This migration...
- Seb (18/41) Apr 19 2019 Most D code is yet to be written. Safe by default is sth. that
- Eugene Wissner (2/7) Apr 19 2019 And it isn't good for the documentation.
- Adam D. Ruppe (9/10) Apr 19 2019 This still doesn't work correctly for templates, which should be
- Jonathan M Davis (7/17) Apr 19 2019 Definitely - though I'm not quite sure how allowing null would work with
- Mike Franklin (18/32) Apr 19 2019 I think we could make it an enum:
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 2019
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 2019
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 2019
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, Mikeimmutable 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 2019
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 2019
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 defaultCan anyone save me some trouble and articulate why it would be bad to have `pure` and/or `immutable` by default? Thanks, MikeCheers, RazvanN
Apr 16 2019
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 defaultSame 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 2019
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 2019
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 2019
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 2019
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, MikeI 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 2019
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 2019
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, MikeI 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 2019
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 2019
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 2019
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: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 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.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 2019
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: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.On Tuesday, 16 April 2019 at 21:33:54 UTC, Jonathan M Davis wrote: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 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.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 2019
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 2019
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 2019
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 2019
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: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.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]`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.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.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.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 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".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 2019
On 4/18/19 2:33 PM, Eugene Wissner wrote:On Thursday, 18 April 2019 at 14:52:53 UTC, Jonathan M Davis wrote:There's a lively discussion at https://github.com/dlang/dlang.org/pull/2627. Specialists welcome.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.
Apr 18 2019
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: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.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.
Apr 17 2019
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: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.On Monday, April 15, 2019 9:59:38 PM MDT Mike Franklin via Digitalmars-d wrote: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.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.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 2019
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 2019
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: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 DavisThere _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 2019
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, MikeI 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 2019
On Friday, 19 April 2019 at 12:23:08 UTC, Andrei Alexandrescu wrote:On 4/15/19 11:59 PM, Mike Franklin wrote:Most D code is yet to be written. Safe by default is sth. that all new D code should beI 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, MikeI 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: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 2019
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:And it isn't good for the documentation.pure: safe:NOPE. That doesn't work as DMD doesn't set pure for member functions.
Apr 19 2019
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 2019
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: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(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 2019
On Friday, 19 April 2019 at 21:46:20 UTC, Jonathan M Davis wrote: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. MikeWe 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.
Apr 19 2019