Updated: Jul 27, 2021
Hey! How's it going?!
I hope you are all well and safe!
This time around I'll try to keep this straight to the business so we can cover as much as possible ground as possible since we never know when I'll have time for the next post.
Just in case you need a refresh, you can take another look at the Introduction to CM Syntaxes article before moving forward here. As promised in that post, we're now going to dig into Statements. Learn what they are and how to leverage them to create nifty solutions for our projects.
Ok, a little back and forth
Echoing the information I've provided in the last article, here's a quick refresh for what Statements are:
...a syntactic unit of an imperative programming language that expresses some action to be carried out... A statement may have internal components (e.g., expressions). - Wikipedia
Or, you could basically think of Statements as Expressions that do NOT return anything.
Knowing that and having already gone through a crash course on Expressions for CM syntaxes we are more than ready to move into actual Statements. As a matter of fact, we're going to jump right in compound statements.
Since we've already covered the "Hello World" theme last time we will be a bit more practical from now on.
Say that we want a shorter, more human-readable way to run a given function on top of all of the items inside a collection. We want that to be usable for both CM Sets and CM Sequences but we definitely don't want to write two different codes for it.
With the use of syntax, that's trivial. Here's what it would look like:
Go ahead, copy that definition along with the following test scenario and take it for a spin:
Cool right?! We even got the method overload to work as well!
Now here's a small challenge, say we want to allow our syntax's consumer to easily say if they want the generated code to automatically skip null objects. How would you pull that off?
As you would - correctly - assume, there's no final/unique solution for this problem. It's a matter of how much time you have to work on it and how deep you want to go creating a solution using that time.
In any case, here's my take on the above problem. Below you will also find some notes on why I choose to code it this way and what are the benefits and highlights of it.
As you probably noticed, the changes are very small and, in fact, the previous syntaxes should still work unchanged (that's one of the goals here - don't break "old" code). Here's the test code:
As promised let's go through a list of interesting things to look for on the above syntax definition code:
We're optimizing the generated code so we don't have to run an unnecessary null check for a collection that doesn't require it (for example, when we're iterating through a collection that holds value types);
We're relying on a core syntax any (available at lang\basic.syntax.cm) to read through the provided parameters' list;
By leveraging the actualArgList type (defined in \base\cm\syntax\SactualArgList.boot.cm) we are actually allowing for even more parameters/customization to be added in the future without the risk of breaking existing code;
In order to further explore this topic, let's see how we can improve on a task that I'm sure you see a lot of different implementations and code replication in every CET project you ever worked on.
I'm talking about collection manipulation.
At the time of this writing (CET 11.5), there are six different collection types available in CM:
Sorted map (index)
Usually, if we want a given logic to be usable/applicable across many ( > 1 ) classes/structures in CM we need to either write X different variants of the same logic or - sometimes - we can hardcode some if statements (along with type checks) to get this done.
By knowing your way around syntax we add a third option to that list. It's fair to say that in general, a good Syntax do NOT care about the underlying type, instead, they only care about contracts and the logic applied on top of it.
This entire set of syntax will be available in my Commom.CET library repo. You can check all of the other helpers I've created in there and even suggest a new one if you have it.
For now, let's start by covering a very basic yet useful case: firstOrDefault
We want to, for a given collection, fetch the very first object that matches certain criteria. It doesn't matter if you're searching for it inside an int, Snapper, or CustomObject Collection. All it matters is the following logic:
Iterate through a given Collection of Type X;
Find the element that matches the predicate X = Y;
If you find it, return that item typed as X;
Otherwise, return default(X);
So, say we have the following array:
[ 1, 2, 3, 4 ]
Then we could potentially have the following two things happening:
When our predicate is y=3 the result would be 3.
When our predicate is y=10 the result would be 0 (or, default(int) ):
Pretty simple, right? It should be.
But - there's always a but - if you try to apply the same logic on a Collection holding a different Object Type, you will NEED a different method that will do EXACTLY the same thing. Line by line. Instruction by instruction. The only difference is that it will look/compare/return another Type instead.
Let's solve it! Hold your breath, we're diving into yet another syntax!
And here, are some use cases we can apply this to:
The above examples are executed not only on top of Collections with different element types but, on top of different Collection types altogether;
It's null safe;
It Stops as soon as we find what we're looking for;
It only allocates the default after failing to find the desired item inside the collection.;
Are you up for a challenge?
I know you are!
Without going into the other variants available on the Github page I've provided you earlier in this article, give it a try and adjust the previous code instead of finding the first item and return that, return a new collection with all the elements that match a certain predicate?
So, in short, here's what you should create (in fact, use this as the comment you should put on the top of your method):
Find all items that fulfills a given condition or returns an empty collection if no items are found.
Go ahead, I will wait.
Core already exposes two native syntaxes with some common names for this feature. Although Core's implementation doesn't do what we want this to do. Here's what we have in there:
findAll: Generic: Get a subset of the supplied list, filtered by type; (From: \base\cm\lang\collectionHelpers.cm)
all: Return true if all elements in @coll satisfy @cond. (From: \base\cm\lang\basic.syntax.cm)
Now that you've created your very own statement I feel that we're ready to put all that we've learned in a bucket and head for the goal! Next time we'll see and explore the final code. We will get to explore what it does and what it doesn't do (and why).
I hope you liked what you've learned so far, stay safe and see you next time!