people[name=="Andrew"].friends ~= peter

Oskar Linde oskar.lindeREM at OVEgmail.com
Fri May 5 08:56:29 PDT 2006


Hi Antonio,

antonio wrote:

> As I introduced in http://www.dsource.org/forums/viewtopic.php?t=967 
> object relations could be seen as hierarchycal structures.
> 
> ¿Why not to introduce native syntax to "navigate into"/"Selec from" this 
> kind of hierarchies?

Interesting idea. This looks like a general short hand way of expressing 
select and map operations. The syntax has some problems though. See the 
comments below.

In my std.array proposal 
http://www.digitalmars.com/d/archives/digitalmars/D/35455.html I have 
implemented .filter() and .map() function templates that allow a way of 
expressing the below examples in a way that work with current D. (Maybe 
.filter() should be renamed .select()?).

I am attaching examples of how your examples would look with my 
std.array syntax. Those examples are rather wordy, mostly because there 
is no short hand notation for declaring single expression delegates. I'm 
not saying my examples show a better way to do things than your 
suggestion (quite the opposite), but they show how those kinds of 
expressions can be written today. My method also has the downside of 
creating and then iterating over temporary arrays. It would be great if 
one could find a way to define iterable array views. I will probably 
look into that soon.

[After rereading, I'm not sure it was a good idea to include this:]
As another perspective, I've played with the thought that D had a way of 
expressing single expression delegates and show how the code would look 
then. This hypothetical syntax is:

{|T x| x+5}

which is equivalent to:

delegate auto(T x) { return x+5; }

where auto is typeof(x+5).

...

> ex 1: Add Peter to the friends of people named Andrew and older than 18.
> 
> Person peter = ...;
> Person[] people = ...;
> 
> people[name=="Andrew" && age>18].friends ~= peter;

With the suggested std.array, the following examples work:

foreach (p; people.filter(delegate bool(Person p) {
                               return p.name == "Andrew" && p.age > 18;
                           }))
     p.friends ~= peter;

people.filter(delegate bool(Person p) {
                   return p.name == "Andrew" && p.age > 18;
               })
       .map(delegate void(Person p) { p.friends ~= peter; });


(I'm not sure about the best way to use whitespace in such code...)

And a hypothetical example with a short hand delegate notation:
people.filter( {|Person p| p.name == "Andrew" && p.age > 18} )
       .map( {|Person p| p.friends ~= peter} );

> 
> ex 2: Obtain the array of not married people childs.
> ex 2.a: with duplicates:
> 
> Person[] modernChilds = people[!married].childs;

I don't fully understand the semantics of this one. I assume 
Person.childs is of type Person[]. In that case, the return type of the 
above would logically be Person[][], not Person[]. In that case:

Person[][] modernChilds = people
          .filter(delegate bool(Person p) { return !p.married; })
          .map(delegate Person[](Person p) { return p.childs; });

With the (IMHO, strange) assumption that the return value should be a 
concatenated array, just add a .join().

Hypothetical:
Person[][] modernChilds = people
          .filter( {|Person p| !p.married } )
          .map( {|Person p| p.childs } );

> ex 2.b: without duplicates:
> 
> Person[] modernChilds = Distinct(people[!married].childs);

Thanks for the idea. .distinct() (or maybe .unique()) is something I 
definitely should add to the std.array proposal. :)

> ex 3: Do something with the married childs of people with friends named 
> Andrew
> ex 3.a: using foreach (1 by 1 evaluation)
> 
> foreach( Person someone; 
> people[friends[name=="Andrew"].length!=0].childs[married] ){
>   someone.doSomething();
> }

foreach(someone; people
     .filter(delegate bool(Person p) {
                 return p.friends.find(delegate bool(Person p) {
                                           return p.name == "Andrew";
                                       }) != -1;
             })
     .map(delegate Person[](Person p) { return p.childs; })
     .join()
     .filter(delegate Person[](Person p){ return p.married; })) {
     someone.doSomething();
}

(Phew)

Or, assuming Person has get-methods, the last map, join, filter, could 
be something like:

     .map(&Person.getChilds).join().filter(&Person.isMarried))

> 
> ex 3.b: Only 1 stament:
> 
> people[friends[name=="Andrew"].length!=0].childs[married].doSomething();

Same as above, but with a .map instead of foreach. (Maybe a void version 
of .map() should be named .each() instead...?)

Hypothetical:
people
  .filter({|Person p| p.friends.contains({|Person p| p.name=="Andrew"})})
  .map( {|Person p| p.childs } )
  .join()
  .filter( {|Person p| p.married } )
  .map( {|Person p| p.doSomething() } );

> 
> ex 3.c: without duplicates
> Distinct(people[friends[name=="Andrew"].length!=0].childs[married].doSomething(); 
> 
> 
> (¿could Distinct be solved with a Template?)

Yes. Something like this (could of course be made more efficient):

import /*std.*/array;
// Double dereference link above for implementation.
// Used for .find() and the MakeDynamic template.

/** Returns an array containing only one occurrence of each element.
     The resulting elements are sorted in order of first occurrence.
*/
template unique(ArrTy) {
   MakeDynamic!(ArrTy) unique(ArrTy arr) {
     MakeDynamic!(ArrTy) ret;
     foreach(uint ix, e; arr) {
       if (arr[0..ix].find(e) == -1)
         ret ~= e;
     }
     return ret;
   }
}

import std.stdio;

void main() {
   writefln("%s","ababcdbdcbdbaba".unique());
}

// Prints "abcd"

(MakeDynamic is just there to support static arrays, such as char[15]).

> ---
> 
> These examples could be solved using "D" standard syntax.
> 
> ex: (thanks to csauls)
> 
> Person[] p = cities[name=="Madrid"].population[age>18 && name=="Peter"];
> 
> is equivalent to
> 
> Person[] p;
> foreach (City x; cities) {
>   with (x) {
>     if (name == "Madrid") {
>       foreach (Person y; population) {
>         with (y) {
>           if (age > 18 && name == "Peter")
>             p ~= y;
>         }
>       }
>     }
>   }
> }
> 
> Basically:
> 
> ARRAY[CONDITION].SOMETHING;
> 
> could be traslated to:
> 
> foreach(T x; ARRAY)
>   with(x)
>     if( CONDITION ) {
>     SOMETHING;
>     }
> 
> When CONDITION contains sub-ARRAY evaluations, it could be expanded as
> 
> foreach(T x; ARRAY)
>   with(x) {
>     bool b;
>     """Expand CONDITION and put result into b""";
>     if( b ) {
>     DOSOMETHING;
>     }
>   }
> 
> Of course, this "expanding" method doesn't solve all the posibilities...
> 
>     people[age>10] = new Person("Foo");
> 
> ---
> 
> The main discussion about this idea was focused in 2 points:
> 1. "dot" or "not dot":
>     people[.married && .age>18]
>     vs
>     people[married && age>18]
> 
>    "not dot" is more "D" syntax compliat (thanks to csaul).

This has the same problem as arr[length].
Assume you have the following piece of code:

void petersBirthday() {
	Person people[] = everyone();
	int index = people.indexOf(peter);
	people[index].age++;
}

What happens if Person contains a member called index?

> 2.Array syntax vs "Template" syntax:
>     people[married && age>18]
>     vs
>     people![married && age>18]
> 
>   Personally, I prefer "Array syntax":
>     Person p = people[5];
>     Person[] p = people[3..5]; // 3..5 is a "condition"
>     Person[] p = people[5]; // ¿why not?
>     Person[] p = people[married];
>     Person[] p = people[age>18 && married];
>      

The array syntax seems ambiguous. I think normal (single element) 
indexing needs to have a different syntax from selection/filtered 
indexing. (Unless D had a more stringent separation of bool vs numeric 
variables.)

You also suggest that T should be implicitly convertible to T[] of 
length 1. Is that a good idea?

How would this work with user defined containers?

Best regards,

Oskar



More information about the Digitalmars-d mailing list