The term scalar is often used in PowerShell issues and documentation along with e.g. the about_Comparison_Operators document. I think, I do have an abstract understanding of its meaning (in fact I am using the word myself quite often) but I am unsure about the concrete PowerShell definition.
Scalar data type (Wikipedia):
A scalar data type, or just scalar, is any non-composite value.
Converting from scalar types (about_Booleans)
A scalar type is an atomic quantity that can hold only one value at a time.
But how exactly would I (script wise) check for a scalar in PowerShell?
E.g. should a DateTime Struct and a collection with single item (as [Int[]]1) considered a scalar?
There is no concrete scalar type or interface definition, to do something like:
If ($Something -is [Scalar]) { ...
So, I guess the concrete PowerShell definition is something like:
$IsScalar =
($_ -isnot [Management.Automation.PSCustomObject]) -and
($_ -isnot [ComponentModel.Component]) -and
($_ -isnot [Collections.IDictionary]) -and # Probably covered by ICollection
($_ -isnot [Collections.ICollection])
But not sure if that actually covers it.
To add to the helpful information in your question and in the comments:
There are two, context-dependent definitions of what you might loosely call "scalar" in PowerShell, and while they technically differ, the difference typically doesn't matter:
Note:
In enumeration contexts, notably in the pipeline, and on the LHS of comparison operators, among others:
A "scalar" is any object that cannot be or isn't auto-enumerated; that is, it is treated as a single object.
The behavior is primarily based on whether a type implements the
IEnumerableinterface, with selective, hard-coded exceptions; an informal summary is:See the next section for details.
In to-Boolean coercions (conversions):
A "scalar" is any object that isn't list-like, as exclusively determined by whether its type implements the
IListinterface.[1]A quick summary of the to-Boolean coercion (conversion) logic, which applies to implicit coercions, such as in conditionals, as well as to explicit ones with a
[bool]cast):List-like objects that contain no elements are always
$false, those that contain only one object (element) are treated as that object (see below), whereas those with two or more elements are always$true, irrespective of what elements they contain; e.g.,[bool] @($false)is$false, but[bool] @($false, $false)is$trueThe following yield
$false:0value, regardless of the specific numeric type'')$null, including the "enumerable null", i.e. the special singleton[2] that signals "no output" from commands, but is treated like$nullin expressions; e.g. both[bool] $nulland[bool] (Get-ChildItem NoSuchFiles*)yield$false.Any other object yields
$true, notably including:[bool] 'foo'and[bool] 'false'[bool] [pscustomobject] @{},[bool] (, @($false))is$true(but a nested empty list is$false, e.g.[bool] (, @()))The - comparatively rare - scenarios where the two definitions of "scalar" do make a difference:
Since the
IListinterface (ultimately) derives fromIEnumerable, and none of theIEnumerableexceptions in enumerable contexts implementIList, the difference comes down to the following scenarios:IEnumerable-implementing types that do not also implementIList, e.g. the objects returned by[System.Linq.Enumerable]::Range():IEnumerator-implementing types; e.g., the object returned by an explicit.GetEnumerator()call:System.Data.DataTable, the only non-IEnumerabletype that PowerShell auto-enumerates in enumeration contexts."Scalars" in enumeration contexts:
Enumeration contexts are:
The pipeline - as also implicitly used by a single command, e.g.
Get-ChildItem $HOMESelect operators:
The LHS operands of comparison operators.
The relevant operand of the containment operators,
-inand-contains$(...), the subexpression operator and@(...), the array subexpression operator. The reason that these are enumeration contexts is that they are in essence nested pipelines.The inputs to select language statements (keyword-based statements) (represented as
…below):foreach(foreach ($elem in …) { <# body #> })switch(switch (…) { <# body #> })In any such enumeration context, a "scalar" is any object that cannot be or isn't auto-enumerated.
Auto-enumeration means:
IEnumerableinterface (e.g., enumeration of an array returns its elements).That is:
It doesn't matter whether a "scalar" object is a composite value (has properties) or not (e.g. a .NET primitive type such as
[int]).While whether an instance's type implements the
IEnumerableinterface (which makes them enumerable on demand from .NET's perspective) is the basis for PowerShell's decision whether to auto-enumerator or not, there are selective, hard-coded exceptions, detailed below.To get PowerShell to enumerate instances of such types on demand, a prominent example of which are hashtables, their
.GetEnumerator()method must be called explicitly; e.g., the following enumerates the key-value pairs (System.Collections.DictionaryEntryinstances) that make up the sample hashtable and stringifies each; without.GetEnumerator(), the hashtable instance as a whole would be sent to the pipeline:@{ foo = 1; bar = 2 }.GetEnumerator() | ForEach-Object ToStringThe fact that this works implies yet another non-"scalar": any object whose type implements the
IEnumeratorinterface, as implemented byIEnumerable-implementing types.[3]The following function encapsulates the exact logic PowerShell uses to determine automatic enumerability;[4] it accepts any object and indicates whether PowerShell would auto-enumerate it in enumeration contexts.
An informal summary of the above:
.NET enumerables except strings, dictionaries,[5] and XML nodes are auto-enumerated.
Additionally - even though they are not enumerables per se - the following are automatically enumerated too:
.Rowsproperty)[1] Here's the link to the source code, as of this writing (this logic is highly unlikely to change, however).
[2] This special singleton is
[System.Management.Automation.Internal.AutomationNull]::Value]; for more information, see this answer.[3] The same applies to types implementing the generic counterparts of these interface,
IEnumerator`1andIEnumerable1`, given that these derive from their non-generic cousins.[4] Here's the link to the source code, as of this writing (this logic is highly unlikely to change, however).
[5] Note that PowerShell only tests for the non-generic dictionary interface,
System.Collections.IDictionary, not also for its generic counterpart,System.Collections.Generic.IDictionary`2. Since the latter does not derive from the former - unlike in theIEnumerable/IEnumerable`1pair - types that implement only the generic interface unexpectedly are auto-enumerated; a prominent example isSystem.Dynamic.ExpandoObject; see GitHub issue #15204 for a discussion of this problematic behavior.