View mode: basic / threaded / horizontal-split · Log in · Help
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
Lutger:
> alias Flags!q{ do_nothing, 
>                walk_dog, 
>                cook_breakfast,
>                deliver_newspaper,
>                visit_miss_kerbopple,
>                wash_covers } Todo;

That's better.
But you have missed the optional type :-)

Bye,
bearophile
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
bearophile wrote:

> Lutger:
>> alias Flags!q{ do_nothing,
>>                walk_dog,
>>                cook_breakfast,
>>                deliver_newspaper,
>>                visit_miss_kerbopple,
>>                wash_covers } Todo;
> 
> That's better.
> But you have missed the optional type :-)
> 
> Bye,
> bearophile

Thanks. I omitted the type because I was lazy. :) Assuming uint it is 
possible to just use std.intrinsic for bit twiddling in the range, since I'm 
not so good with that. I can brush it up and post the code later if you want, 
but it's not so pretty.
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
Lutger:
>I can brush it up and post the code later if you want, but it's not so pretty.<

This syntax is not pretty, but I think it can be acceptable:

alias Flags!q{
   do_nothing,
   walk_dog,
   cook_breakfast,
   deliver_newspaper,
   visit_miss_kerbopple,
   wash_covers
} Todo;

With optional type too:

alias Flags!q{ int :
   do_nothing,
   walk_dog,
   cook_breakfast,
   deliver_newspaper,
   visit_miss_kerbopple,
   wash_covers
} Todo;


Now it's a matter of writing a good amount of CTFE to implement the little parser, to create the methods and attributes, to perform the necessary sanity tests in all inputs, to write documentation, tests, etc :o)
I don't need flags often in my code so don't implement it for me. If you have some free time you can write it for other people here. Once the code is good probably Andrei will accept to put it in Phobos2.

Bye,
bearophile
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
KennyTM~ Wrote:

> On Mar 26, 10 18:52, yigal chripun wrote:
> > KennyTM~ Wrote:
> >
> >> On Mar 26, 10 05:46, yigal chripun wrote:
> >>>
> >>> while it's true that '?' has one unicode value for it, it's not true for all sorts of diacritics and combine code-points. So your approach is to pass the responsibility for that to the end user which in 99.9999% will not handle this correctlly.
> >>>
> >>
> >> Non-issue. Since when can a character literal store>  1 code-point?
> >
> > character != code-point
> >
> > D chars are really as you say code-points and not always complete characters.
> >
> > here's a use case for you:
> > you want to write a fully unicode aware search engine.
> > If you just try to match the given sequnce of code-points in the search term, you will miss valid matches since, for instance you do not take into account permutations of the order of combining marks.
> > you can't just assume that the code-point value identifies the character.
> 
> Stop being off-topic. '?' is of type char, not string. A char always 
> holds an octet of UTF-8 encoded sequence. The numerical content is 
> unique and well-defined*. Therefore adding 4 to '?' also has a meaning.
> 
> * If you're paranoid you may request the spec to ensure the character is 
> in NFC form.

Huh? You jump in in the middle of conversation and I'm off-topic?

Now, to get back to the topic at hand:

D's current design is:
char/dchar/wchar are integral types that can contain any value/encoding even though D prefers Unicode. This is not enforced.  
e.g. you can have a valid wchar which you increment by 1 and get an invalid wchar. 

Instead, Let's have proper well defined semantics in D:

Design A: 
char/wchar/dchar are defined to be Unicode code-points for the respective encodings. These is enforces by the language so if you want to define a different encoding you must use something like bits!8
arithmetic on code-points is defined according to the Unicode  standard. 

Design B: 
char represents a (perhaps multi-byte) character. 
Arithmetic on this type is *not* defined.

In either case these types should not be treated as plain integral types.
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
On Mar 28, 10 18:56, Yigal Chripun wrote:
> KennyTM~ Wrote:
>
>> On Mar 26, 10 18:52, yigal chripun wrote:
>>> KennyTM~ Wrote:
>>>
>>>> On Mar 26, 10 05:46, yigal chripun wrote:
>>>>>
>>>>> while it's true that '?' has one unicode value for it, it's not true for all sorts of diacritics and combine code-points. So your approach is to pass the responsibility for that to the end user which in 99.9999% will not handle this correctlly.
>>>>>
>>>>
>>>> Non-issue. Since when can a character literal store>   1 code-point?
>>>
>>> character != code-point
>>>
>>> D chars are really as you say code-points and not always complete characters.
>>>
>>> here's a use case for you:
>>> you want to write a fully unicode aware search engine.
>>> If you just try to match the given sequnce of code-points in the search term, you will miss valid matches since, for instance you do not take into account permutations of the order of combining marks.
>>> you can't just assume that the code-point value identifies the character.
>>
>> Stop being off-topic. '?' is of type char, not string. A char always
>> holds an octet of UTF-8 encoded sequence. The numerical content is
>> unique and well-defined*. Therefore adding 4 to '?' also has a meaning.
>>
>> * If you're paranoid you may request the spec to ensure the character is
>> in NFC form.
>
> Huh? You jump in in the middle of conversation and I'm off-topic?
>

Yes. The original discussion is on implicit conversion, which leads to 
whether ('x' + 1) is semantically correct. How will this be related to 
search engine?

(Technically even this is off-topic. The title said implicit *enum* 
conversion.)

> Now, to get back to the topic at hand:
>
> D's current design is:
> char/dchar/wchar are integral types that can contain any value/encoding even though D prefers Unicode. This is not enforced.
> e.g. you can have a valid wchar which you increment by 1 and get an invalid wchar.
>

Wrong. Read the specs: http://digitalmars.com/d/1.0/type.html, 
http://digitalmars.com/d/2.0/type.html

 * char  = unsigned 8 bit UTF-8
 * wchar = unsigned 16 bit UTF-16
 * dchar = unsigned 32 bit UTF-32

To contain any encoding, use ubyte.

> Instead, Let's have proper well defined semantics in D:
>
> Design A:
> char/wchar/dchar are defined to be Unicode code-points for the respective encodings. These is enforces by the language so if you want to define a different encoding you must use something like bits!8
> arithmetic on code-points is defined according to the Unicode  standard.
>
> Design B:
> char represents a (perhaps multi-byte) character.
> Arithmetic on this type is *not* defined.
>
> In either case these types should not be treated as plain integral types.
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
On 03/28/2010 03:44 AM, Lutger wrote:
> bearophile wrote:
>
>> To invent a software you can first find the best syntax. This seems a nice
>> syntax, very similar to the enum one (that ubyte is optional):
>>
>> flags ubyte Todo {
>>      do_nothing,
>>      walk_dog,
>>      cook_breakfast,
>>      deliver_newspaper,
>>      visit_miss_kerbopple,
>>      wash_covers
>> }
>>
>> Todo todo = Todo.walk_dog | Todo.deliver_newspaper | Todo.wash_covers;
>> if (todo == (Todo.walk_dog | Todo.deliver_newspaper)) { ...
>> if ((Todo.walk_dog | Todo.deliver_newspaper) in todo) { ...
>> if ((Todo.walk_dog | Todo.deliver_newspaper)&  todo) { ...
>> assert((Todo.walk_dog | Todo.walk_dog) == Todo.walk_dog); // OK
>>
>>
>> A way to implement it with current D2 syntax:
>>
>>
>> alias Flags!(ubyte, "do_nothing",
>>                      "walk_dog"
>>                      "cook_breakfast"
>>                      "deliver_newspaper"
>>                      "visit_miss_kerbopple"
>>                      "wash_covers") Todo;
>>
>>
>> Where Flags defines a struct, "do_nothing" are compile-time constants. It
>> can overload 8 operators:
>> =   ==   |    |=    in&    &=  opBool
>>
>> The operator ! too can be defined, but I think it looks too much like the |
>> so it can be omitted (other operators like ^ and ~ are possible).
>>
>
> I like this idea of implementing a flag type and tried to work something out.
> Instead of implementing the overloads, it is also possible to generate an
> enum via CTFE inside a struct and forward with alias this, what do you think?
> I have tried this syntax, seems to work ok:
>
> alias Flags!q{ do_nothing,
>                 walk_dog,
>                 cook_breakfast,
>                 deliver_newspaper,
>                 visit_miss_kerbopple,
>                 wash_covers } Todo;
>
> It does allow this though, but perhaps that can fixed:
>
> Todo todo = Todo.walk_dog;
> todo |= 4;
>
> With such a type, it is easy to add some small convenience features, such as
> an  enumToString, define property .max and implement a range that iterates
> over all flags set or possible values.

I think it will take some time to settle the competition between 
"template parses everything" approach and "language parses most" approach.

For what it's worth, Walter and I were talking three years ago about a 
"new alias" feature:

template Flags(new alias Names...) { ... }

"new alias" means that you may pass to Flags symbols that haven't been 
defined. With that feature in place, Flags could be used like this:

 alias Flags!(do_nothing,
              walk_dog,
              cook_breakfast,
              deliver_newspaper,
              visit_miss_kerbopple,
              wash_covers)
     Todo;

No muss, no fuss, no need to stringize anything. Flags could get to the 
names of the alias parameters by using Names[i].stringof.

Well that's not in the language yet :o).


Andrei
March 28, 2010
Re: Implicit enum conversions are a stupid PITA
Andrei Alexandrescu:
> For what it's worth, Walter and I were talking three years ago about a 
> "new alias" feature:
> 
> template Flags(new alias Names...) { ... }
> 
> "new alias" means that you may pass to Flags symbols that haven't been 
> defined. With that feature in place, Flags could be used like this:
> 
>   alias Flags!(do_nothing,
>                walk_dog,
>                cook_breakfast,
>                deliver_newspaper,
>                visit_miss_kerbopple,
>                wash_covers)
>       Todo;
> 
> No muss, no fuss, no need to stringize anything.

Maybe it's better to go all the way and just add symbols to the language, as in Ruby, Lisps, etc. I don't know. The #walk_dog syntax is not available to represent a symbol "walk_dog", I presume...

Bye,
bearophile
March 29, 2010
Re: Implicit enum conversions are a stupid PITA
Andrei Alexandrescu wrote:

> On 03/28/2010 03:44 AM, Lutger wrote:
(...)
>> I like this idea of implementing a flag type and tried to work something
>> out. Instead of implementing the overloads, it is also possible to
>> generate an enum via CTFE inside a struct and forward with alias this,
>> what do you think? I have tried this syntax, seems to work ok:
>>
>> alias Flags!q{ do_nothing,
>>                 walk_dog,
>>                 cook_breakfast,
>>                 deliver_newspaper,
>>                 visit_miss_kerbopple,
>>                 wash_covers } Todo;
>>
>> It does allow this though, but perhaps that can fixed:
>>
>> Todo todo = Todo.walk_dog;
>> todo |= 4;
>>
>> With such a type, it is easy to add some small convenience features, such
>> as
>> an  enumToString, define property .max and implement a range that iterates
>> over all flags set or possible values.
> 
> I think it will take some time to settle the competition between
> "template parses everything" approach and "language parses most" approach.
> 
> For what it's worth, Walter and I were talking three years ago about a
> "new alias" feature:
> 
> template Flags(new alias Names...) { ... }
> 
> "new alias" means that you may pass to Flags symbols that haven't been
> defined. With that feature in place, Flags could be used like this:
> 
>   alias Flags!(do_nothing,
>                walk_dog,
>                cook_breakfast,
>                deliver_newspaper,
>                visit_miss_kerbopple,
>                wash_covers)
>       Todo;
> 
> No muss, no fuss, no need to stringize anything. Flags could get to the
> names of the alias parameters by using Names[i].stringof.
> 
> Well that's not in the language yet :o).
> 
> 
> Andrei

That would be nice, perhaps it's possible for a backwards-compatible 2.x 
release to work something out. As bearophile said Ruby has some nice things 
for metaprogramming that help with this, borrowed from lisp of course. I do 
think Ruby's strong metaprogramming helped RoR become popular.

In the meantime I tried to hack Flags together and got stuck on this thing:

auto myTodoList = Todo.do_nothing;

Now myTodoList is of type uint (or whatever), not so nice - it's inconsistent 
with regular named enums. It messed up making a range for iterating over the  
flags set, which I thought was a nice addition. 

Anyway, here is my attempt so far, please forgive me it is not so elegant:


import std.conv;

string genFlags(string names, string type = "uint")
{
   string result = "enum : " ~ type ~ " { ";

   int bits = 1;
   size_t pos;
   // skip leading whitespace
   while( ++pos < names.length && names[pos] == ' ') { }
   size_t prev = pos;
   bool hasInitializer = false;

   while ( pos < names.length)
   {
       if (names[pos] == '=')
           hasInitializer = true;
       else if (names[pos] == ',')
       {
           result ~= names[prev..pos];

           if (!hasInitializer)
           {
               result ~= " = " ~ std.conv.to!string(bits);
               bits <<= 1;
           }
           else
               hasInitializer = false;
           result ~= ',';
           //skip whitespace
           while( ++pos < names.length && names[pos] == ' ') { }
           prev = pos;
       }
       pos++;
   }

   // concat the last flag
   if (hasInitializer)
       return result ~ names[prev..pos] ~ "}";
   return result ~ names[prev..pos] ~ " = " ~ std.conv.to!string(bits) ~ 
"}";
}

struct Flags(string members, T = uint)
{
   static assert( is(T : ulong), "Wrong underlying type of Flags: must be 
integral not " ~ T.stringof );

   mixin( genFlags(members) );
   alias typeof(this) FlagsType;

   T value;
   alias value this;

   this(T value)
   {
       this.value = value;
   }

   void opAssign(T value)
   {
       this.value = value;
   }

   pure bool opBinaryRight(string op, E)(E lhs)  const
       if ( op == "in" )
   {
       return cast(bool)(lhs & this);
   }
}

unittest
{
   alias Flags!q{ do_nothing, walk_dog, cook_breakfast, deliver_newspaper,
       visit_miss_kerbopple, morning_task = walk_dog | cook_breakfast,
       wash_covers } Todo;

   Todo list1 = Todo.do_nothing;
   assert( list1 == 1 );
   list1 |= Todo.wash_covers | Todo.walk_dog;
   assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);
   assert(Todo.do_nothing in list1);

   Todo list2 = Todo.cook_breakfast | Todo.wash_covers;
   assert( list1 & list2 == Todo.do_nothing | Todo.cook_breakfast);
   list1 = list2;
   assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);

   assert( Todo.morning_task == Todo.walk_dog | Todo.cook_breakfast );

   auto list3 = Todo.deliver_newspaper;
   assert(Todo.deliver_newspaper in list3, "can't infer type properly"); /* 
bug */
}
March 29, 2010
Re: Implicit enum conversions are a stupid PITA
Lutger wrote:

...

> struct Flags(string members, T = uint)
> {
>     static assert( is(T : ulong), "Wrong underlying type of Flags: must be
> integral not " ~ T.stringof );
> 
>     mixin( genFlags(members) );

I screwed up of course, this must be:

mixin( genFlags(members, T.stringof) );


>     alias typeof(this) FlagsType;
> 
>     T value;
>     alias value this;
> 
>     this(T value)
>     {
>         this.value = value;
>     }
> 
>     void opAssign(T value)
>     {
>         this.value = value;
>     }
> 
>     pure bool opBinaryRight(string op, E)(E lhs)  const
>         if ( op == "in" )
>     {
>         return cast(bool)(lhs & this);
>     }
> }
...
March 29, 2010
Re: Implicit enum conversions are a stupid PITA
Lutger wrote:

...
> 
> unittest
> {
>     alias Flags!q{ do_nothing, walk_dog, cook_breakfast, deliver_newspaper,
>         visit_miss_kerbopple, morning_task = walk_dog | cook_breakfast,
>         wash_covers } Todo;
> 
>     Todo list1 = Todo.do_nothing;
>     assert( list1 == 1 );
>     list1 |= Todo.wash_covers | Todo.walk_dog;
>     assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);
>     assert(Todo.do_nothing in list1);
> 
>     Todo list2 = Todo.cook_breakfast | Todo.wash_covers;
>     assert( list1 & list2 == Todo.do_nothing | Todo.cook_breakfast);
>     list1 = list2;
>     assert(list1 == Todo.do_nothing | Todo.wash_covers | Todo.walk_dog);
> 
>     assert( Todo.morning_task == Todo.walk_dog | Todo.cook_breakfast );
> 
>     auto list3 = Todo.deliver_newspaper;
>     assert(Todo.deliver_newspaper in list3, "can't infer type properly");
>     /*
> bug */
> }

       ^ 
oops, this one is totally messed up and needs to go to precedence school. 
This should be better:

unittest
{
   alias Flags!q{ do_nothing, walk_dog, cook_breakfast, deliver_newspaper,
                  visit_miss_kerbopple, morning_task = walk_dog | 
cook_breakfast, wash_covers } Todo;

   Todo list1 = Todo.do_nothing;
   assert( list1 == 1 );
   list1 |= Todo.wash_covers | Todo.walk_dog;
   assert(list1 == (Todo.do_nothing | Todo.wash_covers | Todo.walk_dog) );
   assert(Todo.do_nothing in list1);

   Todo list2 = Todo.cook_breakfast | Todo.wash_covers;
   assert( (list1 & list2) == Todo.wash_covers );
   list1 = list2;
   assert(list1 == (Todo.cook_breakfast | Todo.wash_covers) );

   assert( Todo.morning_task == (Todo.walk_dog | Todo.cook_breakfast) );

   auto list3 = Todo.deliver_newspaper;
   /* bug */
   assert(Todo.deliver_newspaper in list3, "can't infer type properly");
}
Next ›   Last »
6 7 8 9 10
Top | Discussion index | About this forum | D home