foreach keyword in CRMScript?

Hi,

we write CRMScript all day. Everyday. All the time.

One thing that I wish CRMScript had was support for foreach.

So instead of this long syntax

for (Integer i = 0; i < contacts.length(); i++)
{
  NSContact contact = contacts[i];
  //...
}

I wish we could use this shorter variant

foreach (NSContact contact in contacts)
{
  //...
}

What do you other CRMScript developers out there think?

RE: foreach keyword in CRMScript?

Yes, for gods sake, yes!

Av: Pär Pettersson 11. jun 2020

RE: foreach keyword in CRMScript?

We really appreciate your enthusiasm for CRMScript :-)

Coming soon to a release near you 👍

Sverre

Av: Sverre Hjelm 15. jun 2020

RE: foreach keyword in CRMScript?

Sweet :)

Av: Simen Mostuen Iversen 15. jun 2020

RE: foreach keyword in CRMScript?

Great addition! While this work for all types?

Av: David Hollegien 15. jun 2020

RE: foreach keyword in CRMScript?

Nice, thank you very much!

Av: Frode Lillerud 16. jun 2020

RE: foreach keyword in CRMScript?

Yes, it will work for any type, including your own structs. Here is my test-script which shows some variations:

#setLanguageLevel 3;

String[] array;
array.pushBack("Time");
array.pushBack("is");
array.pushBack("an");
array.pushBack("illusion");

foreach (String s in array) {
  print(s + " ");
  s = s + " was here"; // Does not change array elements
}

struct Person {
  String firstname;
  
  Void print() {
    printLine("Person: " + this.firstname);
  }
};

Person Person(String firstname) {
  Person p;
  p.firstname = firstname;
  return p;
}

Person[] persons;

persons.pushBack(Person("Steve"));
persons.pushBack(Person("Bill"));

foreach (Person p in persons) {
  p.print();
  p.firstname = p.firstname + " was here"; // Changes array elements
}

foreach (Person p in persons) 
  p.print();

Note: foreach'ing an array of basic types, such as strings, will not let you manipulate them as they are "pass by value", while all other types can be manipulated as they are "pass by reference". This is standard CRMScript behaviour.

Also, fun fact: Stian did some performance testing and it seems that foreach is twice as fast as index-looping.

Sverre

Av: Sverre Hjelm 16. jun 2020

RE: foreach keyword in CRMScript?

Nice :D 

Av: Suran Basharati 16. jun 2020

RE: foreach keyword in CRMScript?

Sverre,

Sounds great! Especially the performance improvement!

Now that we are on the topic of improvements, next to writing for loops I think that following is something that I type multiple times a day:

SearchEngine se;

// configure search

for (se.execute(); !se.eof(); se.next())
{
    // process 
}

Would it be possible to also create a short-hand for the execute(); eof(); next() syntax? Would same a lot of repeated key strokes!

Av: David Hollegien 16. jun 2020

RE: foreach keyword in CRMScript?

Very nice! 

Btw, any chance for struct and members to be dynamically added to intellisense? 
Would make life easier when you #include other scripts that contain functions/structs/whatever you need to use. 

//Eivind

Av: Eivind Johan Fasting 16. jun 2020

RE: foreach keyword in CRMScript?

Building on Davids example, perhaps something like this. SearchEngine could have a execute method that returns an iterator, and perhaps a new type "Result"(?) would be needed.

SearchEngine se;
se.addField("contact.contact_id");
se.addField("contact.name");
for (Result row in se.executeResult())
{
  Integer contactId = row.get("contact.contact_id").toInteger();
  String name = row.get("contact.name");
}

 

Av: Frode Lillerud 16. jun 2020

RE: foreach keyword in CRMScript?

This is going to be so nice. Here is another useful case with foreach. Imagine you have a global function defined that mimics Python's range function. That function could be defined like this.

// Global helpermethod defined in a common includescript.
// Mimics Pythons range functionality.
Integer[] range(Integer top)
{
  Integer[] values;
  for (Integer i = 0; i < top; i++)
    values.pushBack(i);
  return values;
}

Using the new foreach keyword I think we'd be able to create simple loops like this:

for (Integer i in range(10))
  //Do something 10 times

 

Av: Frode Lillerud 16. jun 2020

RE: foreach keyword in CRMScript?

Nice :D 

Av: Suran Basharati 16. jun 2020

RE: foreach keyword in CRMScript?

I agree that it would be nice to have shorthand for the execute/next/eof pattern. I think the solution embraced by most modern languages is the iterator pattern. That would be something along the lines of what Frode is suggesting.

BUT

The pattern we haved used mostly in CRMScript is where the container has an internal iterator. This is the case for Map, SearchEngine, GenericSearch, AppointmentSlicer, StatResult, File and probably som others. If we were to create iterator classes for all these, it would be quite some work. And the language is not well fitted for creating some kind of generic iterator class either.

So, I am leaning towards some syntactic sugar that would allow you to loop a container-iterator with less typing. The new foreach is not really suited, since it is declared as "foreach" "(" <variable declaration> "in" <returnable> ")". And like I said, we do not have a suitible type for the iterator variable (e.g. a Row in a SearchEngine).

How about something like this:

iterate(gs) {
}

Which would be short hand for

for (gs.excecute(); !gs.eof(); gs.next()) {
}

 

Of course, we would have to come up with some clever solution for the three function names, as they vary a bit from class to class.

Sverre

Av: Sverre Hjelm 16. jun 2020

RE: foreach keyword in CRMScript?

As for the issue Eivind is mentioning: intellisense for the stuff you create in your script. This is not so easy, as the intellisense today is static based on a file loaded from the server. Your script is in your browser, or on the server in various #includes. In order to get intellisense for this, we would have to send what you are typing (which could be incomplete and syntactically wrong) to the server, have it parse it as well as any #includes, and return intellisense data.

Doable, but complicated.

Sverre

Av: Sverre Hjelm 16. jun 2020

RE: foreach keyword in CRMScript?

Intellisense wouldn't need to be live-updated. We'd be fine with having to save the script get updated intellisense. It seems like your are getting the ejScriptIntellisense javascript array from the static /javascript/codemirror/ejscriptIntellisense.js file now. It is possible to append more definitions to that array on the clientside. Manually it can be done with 

ejScriptIntellisense.push({"text": "Car.drive", "help": "<i>Wrooooooom 🚗</i>"})

To get this:

Perhaps the EditScript screen could do a call back to the server to get the additional intellisense definitions based on the Structs in the #include's? Or the EditScreen could create a customEjScriptIntellisense array during loading, which is merged into the ejScriptIntellisense array...? 🙂

If you gave us an event we could hook into we might also be able to make it ourselves. Perhaps a hook-script for action=editScript or someway we could inject some javascript into action=editScript.

Av: Frode Lillerud 16. jun 2020

RE: foreach keyword in CRMScript?

Intellisense for dynamic and included code would be very nice indeed.

The better and more structured code you write, the more need of intellisense there is. :)

I have a best practise where I place all trigger-code in separate scripts, which I then add as an include in the trigger. This to be able to add as much as possible in the Service-package for deployment (as triggers can't be added in a package at the moment).

I also use VSCode and the CRMScript-plugin quite often. If it would be easier to write some dynamic intellisense-handler in that context, that would also be interesting. I think the original idea with this plugin is great! Especially to be able to create and update scripts directly to an Online-tenant-environment. I have only used it for pure development though.

Maybe a bit off-topic for this post though. :)

 

Av: Marcus Svenningsson 16. jun 2020

RE: foreach keyword in CRMScript?

I agree, it's not necessary for live-update, it's enough if saving the script 'refreshes' the intellisense with whatever you have added. 

Something that goes through your code and picks up some kind of indentificator and add them to a customArray sounds reasonable: 

Struct Car {
  Integer id;
  String name;
};

#addToIntellisense(Car, "custom Struct, description goes here");

I will gladly add all the members individually as well, and create my own little function for it, if that makes things easier:

Void addToIntellisense(string text, string help){
  intellisense.push("{\"text\": \"" + text + "\", \"help\": \"<i>" + text + "</i>\"}");
}

Struct Car {
  Integer id;
  String name;
};

addToIntellisense(Car, "custom Struct, description goes here");
addToIntellisense(Car.id, "This is the id");
addToIntellisense(Car.name, "This is the name");

Or let us run javascript on screen load, then we can follow frode's example ;) 

//Eivind

Av: Eivind Johan Fasting 16. jun 2020

RE: foreach keyword in CRMScript?

Hmmm, where's the split Forum Topic button? Must be in the old version....

Please stay on topic... New/Different feature requests should be in a separate thread. 

Good ideas people!

Best regards

Av: Tony Yates 16. jun 2020

RE: foreach keyword in CRMScript?

Started a new thread regarding the intellisense-stuff :) 

//Eivind

Av: Eivind Johan Fasting 16. jun 2020

RE: foreach keyword in CRMScript?

Any thoughts on iterate(), anyone?

Sverre

Av: Sverre Hjelm 17. jun 2020

RE: foreach keyword in CRMScript?

iterate() sounds good to me :) 


//Eivind

Av: Eivind Johan Fasting 17. jun 2020

RE: foreach keyword in CRMScript?

Yes, interate sounds like a good solution!

Av: David Hollegien 17. jun 2020

RE: foreach keyword in CRMScript?

Have given the idea of iterate(type) {...} a little more thought and have a question which may impact foreach.

I know you said the language is not well fitted for creating some kind of generic iterator class, but just brainstorming here... 

Would it not be possible to create a generic KeyValue type that internally transforms a Map, SearchEngine, GenericSearch, AppointmentSlicer, etc when called in a foreach into a KeyValueCollection that can be iterated over? Would this not be what you do anyway in an iterate {...} statement?

What if....the KeyValue Value property exposed relevant accessors?

Map map;
map.insert("1", "one");
map.insert("2", "two");
map.insert("3", "three");
map.insert("4", "four");
map.insert("5", "five");

foreach (KeyValue kv in map) {
  print(kv.getKey + " " + kv.getValue); // key as string, and value is value
}

//-----------------------------------------------------------------------------

SearchEngine se;
se.addField("ticket.cust_id.firstname");
se.addCriteria("ticket.status", "OperatorEquals", "1", "OperatorAnd", 0);

foreach (KeyValue kv in se) {
  print(kv.getKey + " " + kv.getField(0)); // key as string, and getField functions.
  // or
  print(kv.getKey + " " + kv.getValue().getField(0));
}

//-----------------------------------------------------------------------------

String sql = "select id,title from ticket where status = 1";
StatLib sl;
sl.setSql(sql);

StatResult sr;
sl.execute(sr);

foreach (KeyValue kv in sr) {
  print(kv.getKey + " " + kv.getField(0)); // key as string, and getField functions.
  // or
  print(kv.getKey + " " + kv.getValue().getField(0)); // key as string, and getField functions.
}

With regards to the KeyValue.Value property, perhaps this is a good time to make use of the new Generic data type and expose the range of Generic methods either off the KeyValue.Value property, and whatever would be needed for accessing a fields collection?

//-----------------------------------------------------------------------------

SearchEngine se;
se.addField("ticket.cust_id.firstname");
se.addCriteria("ticket.status", "OperatorEquals", "1", "OperatorAnd", 0);

foreach (KeyValue kv in se) {
  print(kv.getKey + " " + kv.getGenericValue()); 
  // or
  print(kv.getKey + " " + kv.getGenericValue(String fieldName)); 
  // or
  print(kv.getKey + " " + kv.getGenericValue(Integer fieldIndex)); 

}


By the way, a search for GenericSearch and AppointmentSlicer didn't return a single result in these forums - apart from this thread. 

Best regards.

Av: Tony Yates 25. jun 2020

RE: foreach keyword in CRMScript?

Tony said: "By the way, a search for GenericSearch and AppointmentSlicer didn't return a single result in these forums - apart from this thread. "

Yes, I don't think we've ever used those either. Perhaps it's time for #setLanguageLevel 4; to be introduced to deprecate some older stuff?

Av: Frode Lillerud 26. jun 2020

RE: foreach keyword in CRMScript?

My thoughts is that the iterate solution would be sweet as well - just as the other suggestions in this post :D

Av: Dennis Aagaard Mortensgaard 30. jun 2020