www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - YesOrNo: useful idiom helper or wanking?

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
A fair amount of code in std uses this idiom:

enum SomeOption { no, yes }

void someFunction(...parms..., SomeOption, ...more_parms...) { ... }

SomeOption is really a Boolean but replaces the unhelpful call syntax 
someFunction(...args..., false, ...more_args...) with the 
self-documenting ...args..., SomeOption,no, ...more_args...).

The liabilities of using Booleans for describing flags have been 
documented in several places, including McConnell's Code Complete. I 
think the idiom above is a good replacement.

One issue I have with it is that it requires separate definition and 
documentation. Usually the documentation awkwardly states "This type is 
really an option for someFunction, which you haven't seen yet. Skip this 
for now, then get back, and you'll understand." Moving the enum to after 
the function is possible but equally confusing.

So I was thinking of planting a simple template in std.typecons:

template YesOrNo(string name)
{
     mixin("enum "~name~" : bool { no, yes }");
}

That way, the definition of the function needs no separately-defined is 
self-documenting:

void someFunction(...parms..., YesOrNo!"SomeOption", ...) { ... }

The question is to what extent this would mark progress and fostering 
structured use of this idiom, versus just confusing beginners and adding 
deadweight to Phobos.



Andrei
Apr 11 2011
next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
It looks like an awkward workaround for that feature called named arguments.
Apr 11 2011
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
Andrej Mitrovic wrote:
 It looks like an awkward workaround for that feature called named
 arguments.

Named arguments aren't a part of standard D now, nor does it look likely they will be for many years. We've gotta look at the here and now to make decisions; focus on what we have rather than what we don't have.
Apr 11 2011
parent KennyTM~ <kennytm gmail.com> writes:
On Apr 11, 11 23:27, Adam D. Ruppe wrote:
 Andrej Mitrovic wrote:
 It looks like an awkward workaround for that feature called named
 arguments.

Named arguments aren't a part of standard D now, nor does it look likely they will be for many years. We've gotta look at the here and now to make decisions; focus on what we have rather than what we don't have.

Well the same argument was used to reject assertPred in favor of an improved assert which we still don't have (issue 5547). (Though changing assert's output doesn't change the D spec, while adding named arguments do, and the D2 spec is frozen.)
Apr 11 2011
prev sibling next sibling parent FeepingCreature <default_357-line yahoo.de> writes:
On 11.04.2011 16:53, Andrei Alexandrescu wrote:
 A fair amount of code in std uses this idiom:
 
 enum SomeOption { no, yes }
 
 void someFunction(...parms..., SomeOption, ...more_parms...) { ... }
 
 SomeOption is really a Boolean but replaces the unhelpful call syntax
 someFunction(...args..., false, ...more_args...) with the
 self-documenting ...args..., SomeOption,no, ...more_args...).
 
 The liabilities of using Booleans for describing flags have been
 documented in several places, including McConnell's Code Complete. I
 think the idiom above is a good replacement.
 
 One issue I have with it is that it requires separate definition and
 documentation. Usually the documentation awkwardly states "This type is
 really an option for someFunction, which you haven't seen yet. Skip this
 for now, then get back, and you'll understand." Moving the enum to after
 the function is possible but equally confusing.
 
 So I was thinking of planting a simple template in std.typecons:
 
 template YesOrNo(string name)
 {
     mixin("enum "~name~" : bool { no, yes }");
 }
 
 That way, the definition of the function needs no separately-defined is
 self-documenting:
 
 void someFunction(...parms..., YesOrNo!"SomeOption", ...) { ... }
 
 The question is to what extent this would mark progress and fostering
 structured use of this idiom, versus just confusing beginners and adding
 deadweight to Phobos.
 
 
 
 Andrei

Named parameters fix this without the need for workarounds. someFunction(...parms..., someOption=>true, ...more parms....);
Apr 11 2011
prev sibling next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 11.04.2011 18:53, Andrei Alexandrescu wrote:
 A fair amount of code in std uses this idiom:

 enum SomeOption { no, yes }

 void someFunction(...parms..., SomeOption, ...more_parms...) { ... }

 SomeOption is really a Boolean but replaces the unhelpful call syntax 
 someFunction(...args..., false, ...more_args...) with the 
 self-documenting ...args..., SomeOption,no, ...more_args...).

 The liabilities of using Booleans for describing flags have been 
 documented in several places, including McConnell's Code Complete. I 
 think the idiom above is a good replacement.

 One issue I have with it is that it requires separate definition and 
 documentation. Usually the documentation awkwardly states "This type 
 is really an option for someFunction, which you haven't seen yet. Skip 
 this for now, then get back, and you'll understand." Moving the enum 
 to after the function is possible but equally confusing.

 So I was thinking of planting a simple template in std.typecons:

 template YesOrNo(string name)
 {
     mixin("enum "~name~" : bool { no, yes }");
 }

 That way, the definition of the function needs no separately-defined 
 is self-documenting:

 void someFunction(...parms..., YesOrNo!"SomeOption", ...) { ... }

 The question is to what extent this would mark progress and fostering 
 structured use of this idiom, versus just confusing beginners and 
 adding deadweight to Phobos.

I presume the user side remains intact and it's just enum declaration that got inculcated into the someFunction declaration, right? (I suspect some folks on the NG might be puzzled by it so I'll try to clarify some points) Like: someFunction(...,SomeOption.yes,...); Then yes it marks certain progress, and I like it. BTW double definition is also nicely solved (i.e. it would be the same template instance). YesOrNo!"Direction" would be the same for all function dealing with "Direction" parameter. Then to save lives of novices reading API docs, we should have some nice short memo about this idiom on the language page or even in the Phobos module listing "common idioms" if we have lots of them, plus links to it everywhere applicable. -- Dmitry Olshansky
Apr 11 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
They aren't a part of D as long as we try to avoid them with
workarounds that make functions look like crap.
Apr 11 2011
prev sibling next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 4/11/11, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 template YesOrNo(string name)
 {
      mixin("enum "~name~" : bool { no, yes }");
 }

 void someFunction(YesOrNo!"SomeOption") { }

How exactly would this work? I can't compile it.
Apr 11 2011
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 11.04.2011 21:13, Andrej Mitrovic wrote:
 On 4/11/11, Andrei Alexandrescu<SeeWebsiteForEmail erdani.org>  wrote:
 template YesOrNo(string name)
 {
       mixin("enum "~name~" : bool { no, yes }");
 }

 void someFunction(YesOrNo!"SomeOption") { }


work, but at the calling site the party ends... -- Dmitry Olshansky
Apr 11 2011
prev sibling parent Stewart Gordon <smjg_1998 yahoo.com> writes:
On 11/04/2011 18:13, Andrej Mitrovic wrote:
 On 4/11/11, Andrei Alexandrescu<SeeWebsiteForEmail erdani.org>  wrote:
 template YesOrNo(string name)
 {
       mixin("enum "~name~" : bool { no, yes }");
 }

 void someFunction(YesOrNo!"SomeOption") { }

How exactly would this work? I can't compile it.

I make out the idea to be that, at the module or class level, you write mixin YesOrNo!("SomeOption"); and then declare functions as, e.g. void doSomething(SomeOption someOption); and so calls to this function become self-documenting with doSomething(SomeOption.yes); The disadvantage is that a user of the library needs to look up SomeOption in order to find out what the possible values are, whereas everybody knows what the possible values of a bool are. It's really a workaround for two things: - named arguments - strong typedefs Stewart.
Apr 11 2011
prev sibling next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Also, I would rather name this template "choice". Maybe if people got
used to this word they would understand it when they see it in the
documentation before a function definition. E.g.:

http://codepad.org/9mrL6MOG or if the site is down:
https://gist.github.com/913926

Otherwise I have no idea how you can put a new type definition inside
of a function parameter and make it public? I'm kind of confused
here..
Apr 11 2011
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
On Apr 12, 11 01:49, Andrej Mitrovic wrote:
 Also, I would rather name this template "choice". Maybe if people got
 used to this word they would understand it when they see it in the
 documentation before a function definition. E.g.:

 http://codepad.org/9mrL6MOG or if the site is down:
 https://gist.github.com/913926

 Otherwise I have no idea how you can put a new type definition inside
 of a function parameter and make it public? I'm kind of confused
 here..

The idea, IIUC, is to avoid documenting that extra enum type. So, for example, TRange topNCopy(alias less = "a < b", SRange, TRange) (SRange source, TRange target, YesOrNo!"SortOutput" sorted = false); and then we can call it as topNCopy([1,3,5,2,4], result, SortOutput.yes); and you don't need to generate the documentation of SortOutput, because we already know from the YesOrNo template that SortOutput can only take 'yes' or 'no'. Your approach is no different from defining Color and Redraw directly. -------- If the goal of YesOrNo is simply for documentation, why not define it like this? import std.stdio; template YesOrNo(T) if(is(T == enum) && !T.no && T.yes) { alias T YesOrNo; } enum Redraw : bool { no, yes } void drawCircle(YesOrNo!Redraw redraw) { writeln(cast(bool) redraw); } void main() { drawCircle(Redraw.yes); drawCircle(Redraw.no); // drawCircle(false); (cannot implicitly convert expression (false) of type bool to Redraw) }
Apr 11 2011
next sibling parent reply KennyTM~ <kennytm gmail.com> writes:
On Apr 12, 11 02:29, Andrej Mitrovic wrote:
 On 4/11/11, KennyTM~<kennytm gmail.com>  wrote:
 The idea, IIUC, is to avoid documenting that extra enum type. So, for
 example,

       TRange topNCopy(alias less = "a<  b", SRange, TRange)
                      (SRange source, TRange target,
                       YesOrNo!"SortOutput" sorted = false);

 and then we can call it as

       topNCopy([1,3,5,2,4], result, SortOutput.yes);

But how do you make `SortOutput` known at the calling site?

Right. Andrei's template can't do it. My approach above is simply declare an undocumented enum, and use YesOrNo as a tag to statically ensure that enum can take 'no' and 'yes'. I feel this using type just for documentation is bad.
Apr 11 2011
parent reply KennyTM~ <kennytm gmail.com> writes:
On Apr 12, 11 03:05, Andrej Mitrovic wrote:
 Are we talking about readability in a code editor or just the website?

The concern is that 'OpenRight' needs to be documented separately from 'until'. With 'YesOrNo', documentation of 'OpenRight' can be omitted.
 Because without some form of syntax highlighting the function headers
 are almost unreadable from the website:
 http://i.imgur.com/B5M6u.png

 It's a wall of text.

Well that's a separate issue. Firstly the struct Until shouldn't appear (e.g. use 'auto' return). Then the signature appeared would become until(alias pred="a==b", Range, Sentinel)(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes); until(alias pred, Range)(Range range, OpenRight openRight = OpenRight.yes); This would break half of the wall.
Apr 11 2011
parent reply "Nick Sabalausky" <a a.a> writes:
"KennyTM~" <kennytm gmail.com> wrote in message 
news:invke1$2gv6$1 digitalmars.com...
 On Apr 12, 11 03:05, Andrej Mitrovic wrote:
 Because without some form of syntax highlighting the function headers
 are almost unreadable from the website:
 http://i.imgur.com/B5M6u.png

 It's a wall of text.

Well that's a separate issue. Firstly the struct Until shouldn't appear (e.g. use 'auto' return). Then the signature appeared would become until(alias pred="a==b", Range, Sentinel)(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes); until(alias pred, Range)(Range range, OpenRight openRight = OpenRight.yes); This would break half of the wall.

...And suddenly people won't know what the hell to expect it to return.
Apr 11 2011
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/11/11 2:41 PM, Nick Sabalausky wrote:
 "KennyTM~"<kennytm gmail.com>  wrote in message
 news:invke1$2gv6$1 digitalmars.com...
 On Apr 12, 11 03:05, Andrej Mitrovic wrote:
 Because without some form of syntax highlighting the function headers
 are almost unreadable from the website:
 http://i.imgur.com/B5M6u.png

 It's a wall of text.

Well that's a separate issue. Firstly the struct Until shouldn't appear (e.g. use 'auto' return). Then the signature appeared would become until(alias pred="a==b", Range, Sentinel)(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes); until(alias pred, Range)(Range range, OpenRight openRight = OpenRight.yes); This would break half of the wall.

...And suddenly people won't know what the hell to expect it to return.

I think it's not all that bad to state until returns a range with such and such characteristics. After all this is the norm in dynamically typed languages. Andrei
Apr 11 2011
prev sibling parent KennyTM~ <kennytm gmail.com> writes:
On Apr 12, 11 03:41, Nick Sabalausky wrote:
 "KennyTM~"<kennytm gmail.com>  wrote in message
 news:invke1$2gv6$1 digitalmars.com...
 On Apr 12, 11 03:05, Andrej Mitrovic wrote:
 Because without some form of syntax highlighting the function headers
 are almost unreadable from the website:
 http://i.imgur.com/B5M6u.png

 It's a wall of text.

Well that's a separate issue. Firstly the struct Until shouldn't appear (e.g. use 'auto' return). Then the signature appeared would become until(alias pred="a==b", Range, Sentinel)(Range range, Sentinel sentinel, OpenRight openRight = OpenRight.yes); until(alias pred, Range)(Range range, OpenRight openRight = OpenRight.yes); This would break half of the wall.

...And suddenly people won't know what the hell to expect it to return.

That's why you assign the result using 'auto'. (This was discussed before, but I can't find the post.)
Apr 11 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/11/11 1:29 PM, Andrej Mitrovic wrote:
 On 4/11/11, KennyTM~<kennytm gmail.com>  wrote:
 The idea, IIUC, is to avoid documenting that extra enum type. So, for
 example,

       TRange topNCopy(alias less = "a<  b", SRange, TRange)
                      (SRange source, TRange target,
                       YesOrNo!"SortOutput" sorted = false);

 and then we can call it as

       topNCopy([1,3,5,2,4], result, SortOutput.yes);

But how do you make `SortOutput` known at the calling site?

I was hasty. I can't see a way for a mixin to introduce at the same time SortOutput and itself in scope. Andrei
Apr 11 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 04/11/2011 08:16 PM, KennyTM~ wrote:
 If the goal of YesOrNo is simply for documentation, why not define it like
this?

 import std.stdio;

 template YesOrNo(T) if(is(T == enum) && !T.no && T.yes) {
      alias T YesOrNo;
 }

 enum Redraw : bool { no, yes }

 void drawCircle(YesOrNo!Redraw redraw) {
      writeln(cast(bool) redraw);
 }

 void main() {
      drawCircle(Redraw.yes);
      drawCircle(Redraw.no);
      // drawCircle(false);  (cannot implicitly convert expression (false) of
 type bool to Redraw)
 }

Very nice, indeed. Also the side-effect that forces the caller to write readable code ;-) Denis -- _________________ vita es estrany spir.wikidot.com
Apr 11 2011
prev sibling parent spir <denis.spir gmail.com> writes:
On 04/11/2011 08:16 PM, KennyTM~ wrote:
 import std.stdio;

 template YesOrNo(T) if(is(T == enum) && !T.no && T.yes) {
      alias T YesOrNo;
 }

 enum Redraw : bool { no, yes }

 void drawCircle(YesOrNo!Redraw redraw) {
      writeln(cast(bool) redraw);
 }

 void main() {
      drawCircle(Redraw.yes);
      drawCircle(Redraw.no);
      // drawCircle(false);  (cannot implicitly convert expression (false) of
 type bool to Redraw)
 }

If this gets used, would be good style to name the enum using the argument name Capitalised, just like in your example (redraw --> Redraw). Denis -- _________________ vita es estrany spir.wikidot.com
Apr 11 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 4/11/11, KennyTM~ <kennytm gmail.com> wrote:
 The idea, IIUC, is to avoid documenting that extra enum type. So, for
 example,

      TRange topNCopy(alias less = "a < b", SRange, TRange)
                     (SRange source, TRange target,
                      YesOrNo!"SortOutput" sorted = false);

 and then we can call it as

      topNCopy([1,3,5,2,4], result, SortOutput.yes);

But how do you make `SortOutput` known at the calling site?
Apr 11 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Are we talking about readability in a code editor or just the website?
Because without some form of syntax highlighting the function headers
are almost unreadable from the website:
http://i.imgur.com/B5M6u.png

It's a wall of text.
Apr 11 2011
prev sibling next sibling parent reply spir <denis.spir gmail.com> writes:
On 04/11/2011 04:59 PM, Andrej Mitrovic wrote:
 It looks like an awkward workaround for that feature called named arguments.

True, but only for the case of yes/no; in this case only, the bool type provides proper *constants* which *meaning* is obvious. Else, you need an enum anyway, even with named args. f = File(path="f.txt", mode=2); Denis -- _________________ vita es estrany spir.wikidot.com
Apr 11 2011
parent bearophile <bearophileHUGS lycos.com> writes:
spir:

 True, but only for the case of yes/no; in this case only, the  bool type 
 provides proper *constants* which *meaning* is obvious. Else, you need an enum 
 anyway, even with named args.
      f = File(path="f.txt", mode=2);

Right, in some cases I prefer an enum and in some cases a named argument. Bye, bearophile
Apr 11 2011
prev sibling next sibling parent spir <denis.spir gmail.com> writes:
On 04/11/2011 07:49 PM, Andrej Mitrovic wrote:
 Also, I would rather name this template "choice".

YesOrNo is far better, by making it clear it's a kind of logical choice / closed question. "Choice" is super vague. Denis -- _________________ vita es estrany spir.wikidot.com
Apr 11 2011
prev sibling next sibling parent reply "Nick Sabalausky" <a a.a> writes:
"Andrei Alexandrescu" <SeeWebsiteForEmail erdani.org> wrote in message 
news:inv4rv$1dfl$1 digitalmars.com...
A fair amount of code in std uses this idiom:

 enum SomeOption { no, yes }

 void someFunction(...parms..., SomeOption, ...more_parms...) { ... }

 SomeOption is really a Boolean but replaces the unhelpful call syntax 
 someFunction(...args..., false, ...more_args...) with the self-documenting 
 ...args..., SomeOption,no, ...more_args...).

 The liabilities of using Booleans for describing flags have been 
 documented in several places, including McConnell's Code Complete. I think 
 the idiom above is a good replacement.

Named args are much better for this. No boilerplate needed. Of course, without named args, yea, this idiom needs to be used.
 One issue I have with it is that it requires separate definition and 
 documentation. Usually the documentation awkwardly states "This type is 
 really an option for someFunction, which you haven't seen yet. Skip this 
 for now, then get back, and you'll understand." Moving the enum to after 
 the function is possible but equally confusing.

/// Documentation here enum SomeOption { no, yes } ///ditto void someFunction(...parms..., SomeOption, ...more_parms...) { ... } That groups the two together, right? So solved.
 So I was thinking of planting a simple template in std.typecons:

 template YesOrNo(string name)
 {
     mixin("enum "~name~" : bool { no, yes }");
 }

 That way, the definition of the function needs no separately-defined is 
 self-documenting:

 void someFunction(...parms..., YesOrNo!"SomeOption", ...) { ... }

 The question is to what extent this would mark progress and fostering 
 structured use of this idiom, versus just confusing beginners and adding 
 deadweight to Phobos.

Not sure what I think, really. Although, I do think the fact there seems to be a need for someting that complex just for a mere two-option setting is more indication that named args are just a better approach. But since we're stuck without that, I really don't know how I feel about this template. Umm, if you need to add a third setting, then you're right back to using a straight enum and making sure it's documentated sensibly. You could generalize YesOrNo further, but I don't think's possible without making it really ugly to use. So maybe it's better to just make sure there's a good way to handle the documentation. If that turns out to require some new DDoc feature, then so be it.
Apr 11 2011
next sibling parent spir <denis.spir gmail.com> writes:
On 04/11/2011 09:35 PM, Nick Sabalausky wrote:
 "Andrei Alexandrescu"<SeeWebsiteForEmail erdani.org>  wrote in message
 news:inv4rv$1dfl$1 digitalmars.com...
 A fair amount of code in std uses this idiom:

 enum SomeOption { no, yes }

 void someFunction(...parms..., SomeOption, ...more_parms...) { ... }

 SomeOption is really a Boolean but replaces the unhelpful call syntax
 someFunction(...args..., false, ...more_args...) with the self-documenting
 ...args..., SomeOption,no, ...more_args...).

 The liabilities of using Booleans for describing flags have been
 documented in several places, including McConnell's Code Complete. I think
 the idiom above is a good replacement.

Named args are much better for this. No boilerplate needed. Of course, without named args, yea, this idiom needs to be used.
 One issue I have with it is that it requires separate definition and
 documentation. Usually the documentation awkwardly states "This type is
 really an option for someFunction, which you haven't seen yet. Skip this
 for now, then get back, and you'll understand." Moving the enum to after
 the function is possible but equally confusing.

/// Documentation here enum SomeOption { no, yes } ///ditto void someFunction(...parms..., SomeOption, ...more_parms...) { ... } That groups the two together, right? So solved.

Yop! The "ditto" feature is great for such cases.
 So I was thinking of planting a simple template in std.typecons:

 template YesOrNo(string name)
 {
      mixin("enum "~name~" : bool { no, yes }");
 }

 That way, the definition of the function needs no separately-defined is
 self-documenting:

 void someFunction(...parms..., YesOrNo!"SomeOption", ...) { ... }

 The question is to what extent this would mark progress and fostering
 structured use of this idiom, versus just confusing beginners and adding
 deadweight to Phobos.

Not sure what I think, really. Although, I do think the fact there seems to be a need for someting that complex just for a mere two-option setting is more indication that named args are just a better approach. But since we're stuck without that, I really don't know how I feel about this template. Umm, if you need to add a third setting, then you're right back to using a straight enum and making sure it's documentated sensibly. You could generalize YesOrNo further, but I don't think's possible without making it really ugly to use. So maybe it's better to just make sure there's a good way to handle the documentation. If that turns out to require some new DDoc feature, then so be it.

I now think the same way. Denis -- _________________ vita es estrany spir.wikidot.com
Apr 11 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 4/11/11 2:35 PM, Nick Sabalausky wrote:
 /// Documentation here
 enum SomeOption { no, yes }
 ///ditto
   void someFunction(...parms..., SomeOption, ...more_parms...) { ... }

 That groups the two together, right? So solved.

Never thought of it. Facepalm etc. Thanks! Andrei
Apr 11 2011
prev sibling parent Bernard Helyer <b.helyer gmail.com> writes:
Named arguments. Seriously. Named arguments. 

*stares into Andrei's soul*
Apr 13 2011