As we learned last time, normally, M code is evaluated in a global identifier resolution scope consisting of all shared members + the standard library. Also, normally, we can’t inject additional identifiers into this global environment. Normally isn’t always. Today, we learn about the exception: where both of these normalities do not apply.
That’s not all: Did you know that M has a mechanism for remembering how to access variables that later go out of scope? Closures open up powerful options, particularly when generating functions…and even enable building an object-like programmatic construct that maintains internal private state and is interacted with through a public interface (kind-of, sort-of somewhat like an object from object-oriented programming!).
Series Index
- Introduction, Simple Expressions &
let
(part 1) - Functions: Defining (part 2)
- Functions: Function Values, Passing, Returning, Defining Inline, Recursion (part 3)
- Variables & Identifiers (part 4)
- Paradigm (part 5)
- Types—Intro & Text (Strings) (part 6)
- Types—Numbers (part 7)
- Types—The Temporal Family (part 8)
- Types—Logical, Null, Binary (part 9)
- Types—List, Record (part 10)
- Tables—Syntax (part 11)
- Tables—Table Think I (part 12)
- Tables—Table Think II (part 13)
- Control Structure (part 14)
- Error Handling (part 15)
- Type System I – Basics (part 16)
- Type System II – Facets (part 17)
- Type System III – Custom Types (part 18)
- Type System IV – Ascription, Conformance and Equality’s “Strange” Behaviors (part 19)
- Metadata (part 20)
- Identifier Scope & Sections (part 21)
- Identifier Scope II – Controlling the Global Environment, Closures (part 22)
- Query Folding I (part 23)
- Query Folding II (part 24)
- Extending the Global Environment (part 25)
- More to come!
A Clean Environment
Remember the very first example from the previous part in this series? We imagined the below expression living in a world with no standard library, no other queries (a.k.a. no section members) and devoid of any other kind of global environment (so not even the standard library is present).
[
a = 1,
b = 2,
c = 3
]
Turns out, we don’t have to imagine this. Thanks to Expression.Evaluate
, we can make such a minimalistic environment a reality.
This standard library function evaluates arbitrary M code, which is provided in textual form as its first argument, returning the result as its output.
Expression.Evaluate("10 + 20") // returns 30
What’s passed in to Expression.Evaluate
defines the entirety of the environment that the provided expression is evaluated in.
The specified expression is evaluated in a section-less world. No sections or section members are present. (This makes sense, as the M code you pass in is an expression, not a section document.) If you’d like to prove that no sections are present, try Expression.Evaluate("#sections")
, and notice that it returns an empty record.
By default, the global environment used when the expression is evaluated is also empty. No standard library is present. Try Expression.Evaluate("#shared")
, which also returns an empty record.
Populating the Global Environment
You can choose to add identifiers to this global environment. This is the purpose of Expression.Evaluate
‘s optional second argument. If provided, it should be a record where each field’s name is the name of an identifier to add to the global environment created by Expression.Evaluate
, with the corresponding field value being the value to associate with that identifier. To emphasize: This record defines the entirety of the global environment used when evaluating the specified expression.
Below populates the global environment used inside Expression.Evaluate
with a (set to 10) and b (set to 20).
Expression.Evaluate(
"a + b",
[a = 10, b = 20] // defines the global environment that's used when evaluating the provided expression
) // returns 30
You can see that the record’s contents are injected into, and define the entirety of, the global environment used by Expression.Evaluate
by asking it to evaluate #shared.
Expression.Evaluate("#shared", [a = 10, b = 20]) // returns [a = 10, b = 20]
If you want to evaluate an expression that references functions from the standard library, you’ll need to add the relevant functions to that global environment.
Expression.Evaluate("Text.Upper(""hi"")") // errors because Text.Upper is not present in the global environment being used
Expression.Evaluate("Text.Upper(""hi"")", [Text.Upper = Text.Upper]) // returns "HI"
Expression.Evaluate
‘s second argument gives fine-tooth control over the environment used when evaluating the specified expression. Expression.Evaluate
‘s purpose is to process arbitrary M code, which in some cases may come from (or be derived from) untrusted sources (say, user input stored in a database). In cases like these, limiting the functions that the expression can use to only those you expect it to (want it to) use is an important security measure to help guard against an injection attack (think of a SQL injection attack, Power Query style).
What if fine-tooth control over what the expression can execute isn’t important in your context? Rather, you want the expression to be evaluated in a “normal” global environment—that is, the same global environment you’d get outside Expression.Evaluate
. Is this possible?
Easy! Set Expression.Evaluate
‘s second argument to #shared.
Expression.Evaluate("Text.ToUpper(""hi"")", #shared) // returns "HI"
(Remember from the last post that #shared returns a record containing one field per identifier in the global environment? This is the exact format Expression.Evaluate
needs to inject these identifiers into the global environment it sets up, so referencing #shared here works perfectly. Just beware of the potential security ramifications.)
Closures
Now, let’s try something which, if we did it to someone else, would be a mean practical joke—but as we’re doing it to ourselves, it’s okay, and will illustrate an important concept. Let’s create a global environment where Text.Lower
is actually defined as Text.Upper
.
Expression.Evaluate("Text.Lower(""Hello"")", [Text.Lower = Text.Upper]) // outputs "HELLO"
Again, don’t do this to someone else. That would be quite unkind.
Let’s keep going with this. In the outer environment, we’ll define a function of our own that formats text. This function, which we’ll also pass in to Expression.Evaluate,
internally uses Text.Lower
.
let
FormatText = (input as text) as text => Text.Lower(input),
EvaluatedExpression = Expression.Evaluate(
"FormatText(""Hello"")",
[
FormatText = FormatText,
Text.Lower = Text.Upper
]
)
in
EvaluatedExpression // what's returned here?
So, the inner global environment created by Expression.Evaluate
contains exactly two identifiers:
- Our FormatText function, which uses
Text.Lower
. Text.Lower
, which maps to the standard library’sText.Upper
.
If, inside Expression.Evaluate
, we invoke FormatText("Hello")
—as the above example does—what will be output?
hello
Wait!!! Shouldn’t that be “HELLO” because inside Expression.Evaluate
, Text.Lower
actually outputs upper cases text, and FormatText uses Text.Lower
?
Ah! This is the lesson that we’re driving at here: FormatText is not using the Text.Lower
that’s present inside Expression.Evaluate
‘s global environment (which up-cases text); rather, it’s using the Text.Lower
that existed in the environment where FormatText was defined.
Remembering the Defining Context
In M, functions “remember” the context in which they were originally defined, even when they are later invoked from a different context. In the nomenclature of programming language theory, this technique for remembering context is called a closure.
When FormatText is defined, a closure is “captured” which enables FormatText to always use the Text.Lower
that existed in its global environment when it was defined (the one that lowercases text). This holds true even though later when FormatText is invoked, the Text.Lower
that’s in the global environment at that point in time is the one that uppercases text. This latter Text.Lower
is irrelevant to FormatText—it doesn’t use it or need it to be present; the Text.Lower
that FormatText uses is the one referenced by its closure.
Closures aren’t limited to just when Expression.Evaluate
is used. Below, Generator is a function that takes in an hourly wage rate and returns a function that takes in hours worked, multiplies it by the hourly rate (which was “captured” by a closure), then returns the result.
let
Generator = (hourlyRate as number) as function =>
(hoursWorked as number) as number => hourlyRate * hoursWorked,
FifteenPerHour = Generator(15)
in
FifteenPerHour(40) // returns 600
The concept is simple yet may feel mind-bending.
To help grasp what’s going on, let’s walk this backward. FifteenPerHour is a variable pointing to a function. We can invoke FifteenPerHour, passing it the number of hours worked, and it will return the amount of wages to be paid.
Where does the function for FifteenPerHour come from? Instead of it being defined the way we’re typically used to, like “FifteenPerHour = (hoursWorked) => hoursWorked
* 15
“, it is defined as the result of a function. Strange as this may feel, this is perfectly valid: a function can return a function, and the function that’s returned can be stored in a variable.
Generator, the function that’s invoked to generate FifteenPerHour‘s function, takes an argument that specifies the hourly wage rate to use. When that function generates the function that actually computes total wages (FifteenPerHour), this rate is “captured” by a closure, which enables the generated function to reference it throughout the generated function’s lifetime.
When FifteenPerHour(40)
is later invoked, nowhere is the hourly rate visible; that amount is not in context at the point of invocation. However, FifteenPerHour knows (remembers) that the rate is 15, thanks to its closure.
Transformer Functions
Closures can be particularly handy when transforming values. Say you have a list which you want to transform using List.Transform
. That function’s second argument is the transformer function to use. It will be called once per list item, each time being passed the current item as its single argument, which it should then transform and return.
List.Transform({ "-abc-", " def "}, Text.Trim) // outputs { "-abc-", "def" }
This is great in many cases (like the above), but what if you want to configure the behavior of the transformer function? For example, in the case of Text.Trim
, you may want to set its optional second argument so that the method trims away a different character.
One option is to build a single-argument function that returns a function which calls Text.Trim
with its second argument hard-coded
let
TrimHyphens = (textToTrim as text) as text => Text.Trim(textToTrim , "-")
in
List.Transform({ "-abc-", " def "}, TrimHyphens ) // outputs { "abc", " def " }
This works great, but your new function is hard-coded to trimming one specific character. What if you wanted to make it configurable? Thanks to closures, this is easy:
TrimCharacter = (characterToTrim as text) as function =>
(textToTrim as text) as text => Text.Trim(textToTrim , characterToTrim)
When TrimCha
racter(“-“) is invoked, it returns a single-argument function, which, when invoked, trims away the specified character from the text passed to it.
let
…,
TrimHyphens = TrimCharacter("-") // generates a function that trims hyphens
in
TrimHyphens("-abc-") // invokes the generated function, returning "abc"
Or, as a one-liner:
TrimCharacter("-")("-abc-") // generates a function that trims hyphens, then invokes it, returning "abc"
Plugging the new TrimCharacter function into our list transformation scenario, we can use it to generate a single-argument function that trims away whatever character we specify. Below, we use it to trim away hyphens. List.Transform
is happy: it’s being passed a single-argument function that takes the current list item as its input and returns the transformed result. We’re happy because we’re using a generic, reusable function to generate the transformer function for List.Transform
.
let
TrimCharacter = (characterToTrim as text) as function =>
(textToTrim as text) as text => Text.Trim(textToTrim , characterToTrim)
in
List.Transform(
{ "-abc-", " def "},
TrimCharacter("-") // generates a single-argument function that trims hyphens, which is then invoked by List.Transform on each list item
) // outputs { "abc", " def " }
All this, thanks to closures, which enable the generated function to “remember” the characterToTrim value that was passed in to the generator function.
Kinda, Sorta Object-Like Behavior
The M language’s support for closures enables a type of object-oritented-like behavior: the ability to create and interact with programmatic structures that expose public interfaces and maintain private internal state.
Of course, since M’s values can’t be modified, each interaction produces a new structure which must be used for the next interaction. (In contrast, an object-oriented language allows the same object instance to be directly manipulated, and manipulated multiple times.)
Let’s imagine a more complex scenario around wage computation. We want an object-like “timecard” entity that allows us to set/change the hourly rate, record hours worked and fetch the total wage amount.
Imagine something that we can interact with like this:
let
…
InitialWage = Timecard(10), // create a new time card and sets the initial hourly wage rate
FirstWeek = InitialWage[RecordHoursWorked](40), // records week 1's work
PayRaise = FirstWeek[SetRate](25), // raises the wage rate for subsequent work
SecondWeek = PayRaise[RecordHoursWorked](40), // records week 2's work
ThirdWeek = SecondWeek[RecordHoursWorked](35), // records week 3's work
TotalWages = ThirdWeek[TotalWages]() // get total wages earned
in
TotalWages // returns 2275
The above starts with a timecard being initialized, with the pay rate set to 10. What’s returned is a record consisting of several fields, each of which contains a function. These functions form the public interface for interacting with the timecard.
This new timecard, saved as InitialWage, is used for FirstWeek‘s recording of hours worked, which is done by using the timecard’s RecordHoursWorked function. This method returns a new timecard with the same set of fields as the previous card, just with the card’s internal state updated to reflect the hours worked.
Next, there’s a PayRaise, which is recorded by invoking SetRate from the FirstWeek timecard’s record. The net effect is another timecard, this one internally updated so that it will record future hours worked at the new pay rate.
Then SecondWeek and ThirdWeek‘s hours worked are recorded by invoking RecordHoursWorked on the timecard from the preceding (respectively) step.
Lastly, TotalWages is output by invoking TotalWages on ThirdWeek‘s timecard. Unlike the card’s other methods, this one doesn’t return a new timecard; rather, it directly returns the amount of total wages as a numeric value.
Internally, timecard maintains the necessary internal state to compute total wages. How it does this is hidden to us, the consumer, and so something we can’t manipulate. Our ability to interact with the timecard is defined by the interface of public methods provided in its record.
The Innerworkings
So how does timecard work?
Timecard = (initialRate as number) =>
let
NewTimecard = (state as record) =>
[
SetRate = (rate as number) =>
@NewTimecard(state & [Rate = rate]),
RecordHoursWorked = (hours as number) =>
@NewTimecard(state & [TotalWages = state[TotalWages] + state[Rate] * hours]),
TotalWages = () =>
state[TotalWages]
]
in
NewTimecard([Rate = initialRate, TotalWages = 0])
Function Timecard takes an initial wage rate which it stores in a state record. This record’s fields, invisible to the outside world, are the approximate equivalent of an object’s private fields.
The state record is then passed to a function defined inside Timecard named NewTimecard. This function returns a record containing the timecard’s “public interface” of three functions, which defines the entirety of how outside code can interact with the card.
Two of these functions, SetRate and RecordHoursWorked, when invoked, call same NewTimecard method, passing it a state record that’s been appropriately updated to reflect the new wage rate or hours worked (respectively). It’s closures that make this possible: from the outside, when you call SetRate or RecordHoursWorked, you have no knowledge of, or access to, this NewTimecard method or the existing state record—but thankfully those two functions do, thanks to closures.
While interesting, typically we don’t write M code this way. We don’t need to. Usually, our interest is in producing a data set, which is achieved by flowing data through a chain of function calls. In contrast, incrementally “manipulating” state by making a series of function calls (as in the last example) is more applicable to when something needs to be programmatically set up for later use.
Like what? For one, how about query folding? When you implement query folding support, Power Query needs an interface to interactively communicate with your implementation, preparing it to produce data. Also, your implementation probably has internal state which Power Query’s folding infrastructure shouldn’t touch or need to know about. This is exactly the kind of situation this style of M coding suits well.
Next Time
This style of sort-of, kind-of object-like M code is foundational for implementing query folding…and covering the basic concepts of implementing folding is the topic for next time.
You may have no plans to ever implement query folding. That’s okay. Understanding the basics of how it’s implemented will help you better understand how it works—and considering the significance query folding holds in the Power Query world, growing your knowledge in this area is a good investment to make.