Typen

Ein Typwert ist ein Wert, der andere Werte klassifiziert. Ein Wert, der durch einen Typ klassifiziert wird, wird als konform mit diesem Typ bezeichnet. Das M-Typsystem besteht aus den folgenden Arten von Typen:

  • Primitiven Typen, die primitive Werte klassifizieren (binary, date, datetime, datetimezone, duration, list, logical, null, number, record, text, time, type) und auch einige abstrakte Typen umfassen (function, table, any, anynonnull und none)

  • Datensatztypen, die Datensatzwerte basierend auf Feldnamen und Werttypen klassifizieren

  • Listentypen, die Listen mithilfe eines einzelnen Elementbasistypen klassifizieren

  • Funktionstypen, die Funktionswerte basierend auf den Typen ihrer Parameter und Rückgabewerte klassifizieren

  • Tabellentypen, die Tabellenwerte basierend auf Spaltennamen, Spaltentypen und Schlüsseln klassifizieren

  • Nullable-Typen, die zusätzlich zu allen von einem Basistyp klassifizierten Werten den Wert NULL klassifizieren

  • Typentypen, die Werte klassifizieren, bei denen es sich um Typen handelt

Die Gruppe der primitiven Typen umfasst die Typen für primitive Werte und einige abstrakte Typen. Letzteres sind Typen, die nicht eindeutig Werte klassifizieren: function, table, any, anynonnull und none. Alle Funktionswerte sind konform mit dem abstrakten Typ function, alle Tabellenwerte mit dem abstrakten Typ table, alle Werte mit dem abstrakten Typ any, alle Werte ungleich NULL mit dem abstrakten Typ anynonnull und keine Werte mit dem abstrakten Typ none. Ein Ausdruck vom Typ none muss einen Fehler auslösen, oder seine Beendigung muss fehlschlagen, da kein Wert erzeugt werden konnte, der mit dem Typ none konform ist. Beachten Sie, dass die primitiven Typen function und table abstrakt sind, da Funktionen bzw. Tabellen nicht direkt von diesem Typ sind. Die primitiven Typen record und list sind nicht abstrakt, da sie für einen offenen Datensatz ohne definierte Felder bzw. eine Liste vom Typ „any“ stehen.

Alle Typen, die nicht zur abschließend definierten Gruppe von primitiven Typen gehören, sowie deren Pendants mit NULL-Wert, werden zusammen als benutzerdefinierte Typen bezeichnet. Benutzerdefinierte Typen können mithilfe eines type-expression (Typausdrucks) geschrieben werden:

type-expression:
      primary-expression

      typeprimary-type
-Typ:
      primary-expression
      primary-type
primary-type:
      primitive-type
      record-type
      list-type
      function-type
      table-type
      nullable-type
primitive-type:
einer der folgenden Werte
      any anynonnull binary date datetime datetimezone duration function list logical
      none null number record table text time type

Bei den Namen der primitiven Typen handelt es sich um kontextabhängige Schlüsselwörter, die nur im Typkontext erkannt werden. Durch die Verwendung von Klammern in einem Typkontext wird zurück zur Grammatik des normalen Ausdruckskontexts gewechselt, weshalb das Schlüsselwort „type“ erforderlich ist, um zurück in den Typkontext zu wechseln. Klammern können zum Beispiel verwendet werden, um eine Funktion in einem Typkontext aufzurufen:

type nullable ( Type.ForList({type number}) )   
// type nullable {number}

Sie können auch verwendet werden, um auf eine Variable zuzugreifen, deren Name mit dem Namen eines primitiven Typs übereinstimmt:

let  record = type [ A = any ]  in  type {(record)} 
// type {[ A = any ]}

Im folgenden Beispiel wird ein Typ definiert, der eine Liste mit Zahlen klassifiziert:

type { number }

Ähnlich wird im folgenden Beispiel ein benutzerdefinierter Typ definiert, der Datensätze mit Pflichtfeldern namens X und Y klassifiziert, deren Werte Zahlen sind:

type [ X = number, Y = number ]

Der einem Wert zugeordnete Typ wird mithilfe der Standardbibliotheksfunktion Value.Type abgerufen, wie in den folgenden Beispielen zu sehen:

Value.Type( 2 )                 // type number 
Value.Type( {2} )               // type list 
Value.Type( [ X = 1, Y = 2 ] )  // type record

Der Operator is wird verwendet, um festzustellen, ob der Typ eines Werts mit einem bestimmten Typ kompatibel ist, wie in den folgenden Beispielen zu sehen:

1 is number          // true 
1 is text            // false 
{2} is list          // true

Der Operator as überprüft, ob der Wert mit dem angegebenen Typ kompatibel ist und löst einen Fehler aus, wenn dies nicht der Fall ist. Andernfalls wird der ursprüngliche Wert zurückgegeben.

Value.Type( 1 as number )   // type number 
{2} as text                 // error, type mismatch

Beachten Sie, dass die Operatoren is und as nur primitive NULL-Typen als rechten Operanden akzeptieren. M stellt keine Möglichkeiten zum Überprüfen der Konformität von Werten mit benutzerdefinierten Typen bereit.

Ein Typ X ist mit einem Typ Y nur dann kompatibel, wenn alle Werte, die mit X konform sind, auch mit Y konform sind. Alle Typen sind kompatibel mit dem Typ any und keine (außer none selbst) mit dem Typ none. Der folgende Graph veranschaulicht die Kompatibilitätsbeziehungen. (Die Typkompatibilität ist reflexiv und transitiv. Sie stellt ein Gitter mit dem Typ any als dem obersten und dem Typ none als dem untersten Wert dar.) Die Namen von abstrakten Typen sind kursiv.

Typkompatibilität

Die folgenden Operatoren sind für Typwerte definiert:

Operator Ergebnis
x = y Gleich
x <> y Ungleich
x ?? y Coalesce

Der native Typ von Typwerten ist der intrinsische Typ type.

Primitive Typen

Typen in der M-Sprache stellen eine getrennte Hierarchie dar, die den Typ any als Stamm hat, der alle Werte klassifiziert. Jeder M-Wert ist mit genau einem primitiven Untertypen von any konform. Die abschließend definierte Gruppe von vom Typ any abgeleiteten primitiven Typen besteht aus den folgenden Typen:

  • type null, der den Wert NULL klassifiziert.
  • type logical, der die Werte „true“ und „false“ klassifiziert
  • type number, der numerische Werte klassifiziert
  • type time, der Zeitwerte klassifiziert
  • type date, der Datumswerte klassifiziert
  • type datetime, der datetime-Werte klassifiziert
  • type datetimezone, der datetimezone-Werte klassifiziert
  • type duration, der Dauerwerte klassifiziert
  • type text, der Textwerte klassifiziert
  • type binary, der Binärwerte klassifiziert
  • type type, der Typwerte klassifiziert
  • type list, der Listenwerte klassifiziert
  • type record, der Datensatzwerte klassifiziert
  • type table, der Tabellenwerte klassifiziert
  • type function, der Funktionswerte klassifiziert
  • type anynonnull, der alle Werte außer NULL klassifiziert
  • type none, der keine Werte klassifiziert

Der Typ „any“

Der Typ any ist abstrakt und klassifiziert alle Werte in M, und alle Typen in M sind kompatibel mit any. Variablen vom Typ any können an alle möglichen Werte gebunden sein. Da any abstrakt ist, kann dieser Typ nicht Werten zugeordnet werden – das heißt, kein Wert ist direkt vom Typ any.

Listentypen

Jeder Wert, bei dem es sich um eine Liste handelt, ist konform mit dem intrinsischen Typ list, bei dem es keine Einschränkungen in Bezug auf die Elemente innerhalb eines Listenwerts gibt.

Listentyp:
      {item-type}
item-type:
      Typ

Das Ergebnis der Auswertung eines Listentyps ist ein Listentypwert, dessen Basistyp list ist.

Die folgenden Beispiele zeigen die Syntax für das Deklarieren von homogenen Listentypen:

type { number }        // list of numbers type 
     { record }        // list of records type
     {{ text }}        // list of lists of text values

Ein Wert ist konform mit einem Listentyp, wenn der Wert eine Liste ist und jedes Element in diesem Listenwert mit dem Elementtyp des Listentyps konform ist.

Der Elementtyp eines Listentyps stellt eine Begrenzung dar: Alle Elemente einer konformen Liste sind mit dem Elementtyp konform.

Datensatztypen

Jeder Wert, bei dem es sich um einen Datensatz handelt, ist konform mit dem intrinsischen Typ „record“, bei dem es keine Einschränkungen in Bezug auf die Feldnamen oder Werte innerhalb eines Datensatzwerts gibt. Datensatztyp-Werte werden verwendet, um die Gruppe gültiger Namen sowie die Typen von Werten, die diesen Namen zugeordnet sein dürfen, zu begrenzen.

Datensatztyp:
      [open-record-marker]
      [field-specification-listopt]
      [field-specification-list , open-record-marker]
field-specification-list:
      field-specification
      field-specification,field-specification-list
field-specification:

      optionalopt field-name field-type-specificationopt
field-type-specification:

      =field-type
field-type:
      Typ
open-record-marker:

      ...

Das Ergebnis der Auswertung eines Datensatztyps ist ein Typwert, dessen Basistyp record ist.

Die folgenden Beispiele zeigen die Syntax für das Deklarieren von Datensatztypen:

type [ X = number, Y = number] 
type [ Name = text, Age = number ]
type [ Title = text, optional Description = text ] 
type [ Name = text, ... ]

Datensatztypen sind standardmäßig geschlossen. Dies bedeutet, dass in konformen Werten keine zusätzlichen Felder vorkommen dürfen, die nicht in der Feldspezifikationsliste vorhanden sind. Durch das Aufnehmen der Kennzeichnung für offene Datensätze in den Datensatztyp wird der Typ als offen deklariert, wodurch Felder, die nicht in der Feldspezifikationsliste vorhanden sind, zulässig sind. Die folgenden beiden Ausdrücke sind äquivalent:

type record   // primitive type classifying all records 
type [ ... ]  // custom type classifying all records

Ein Wert ist mit einem Datensatztyp konform, wenn der Wert ein Datensatz ist, und jede Feldspezifikation im Datensatztyp erfüllt ist. Eine Feldspezifikation ist erfüllt, wenn einer der folgenden Punkte zutrifft:

  • Ein Feldname, der mit dem Bezeichner der Spezifikation übereinstimmt, ist im Datensatz vorhanden, und der zugeordnete Wert ist konform mit dem Typ der Spezifikation.

  • Die Spezifikation ist als optional gekennzeichnet, und im Datensatz wird kein entsprechender Feldname gefunden.

Ein konformer Wert kann nur dann Feldnamen enthalten, die nicht in der Feldspezifikationsliste aufgeführt sind, wenn der Datensatztyp offen ist.

Funktionstypen

Jeder Funktionswert ist mit dem primitiven Typ function konform, bei dem es keine Einschränkungen in Bezug auf die Typen der formalen Parameter der Funktion oder den Rückgabewert der Funktion gibt. Benutzerdefinierte Funktionstypwerte werden verwendet, um Typeinschränkungen für die Signaturen konformer Funktionswerte festzulegen.

Funktionstyp:
      function (parameter-specification-listopt)function-return-type
parameter-specification-list:
      required-parameter-specification-list
      required-parameter-specification-list
,optional-parameter-specification-list
      optional-parameter-specification-list
required-parameter-specification-list:
      required-parameter-specification
      required-parameter-specification
,required-parameter-specification-list
required-parameter-specification:
      parameter-specification
optional-parameter-specification-list:
      optional-parameter-specification
      optional-parameter-specification
,optional-parameter-specification-list
optional-parameter-specification:

      optionalparameter-specification
parameter-specification:
      parameter-name parameter-type
function-return-type:
      assertion
assertion:

      asnullable-primitive-type

Das Ergebnis der Auswertung eines Funktionstyps ist ein Typwert, dessen Basistyp function ist.

Die folgenden Beispiele zeigen die Syntax für das Deklarieren von Funktionstypen:

type function (x as text) as number 
type function (y as number, optional z as text) as any

Ein Funktionswert ist konform mit einem Funktionstyp, wenn der Rückgabetyp des Funktionswerts mit dem Rückgabetyp des Funktionstyps kompatibel ist, und jede Parameterspezifikation des Funktionstyps mit dem formalen Parameter an derselben Stelle der Funktion kompatibel ist. Eine Parameterspezifikation ist kompatibel mit einem formalen Parameter, wenn der als Parametertyp angegebene Typ mit dem Typ des formalen Parameters kompatibel ist. Die Parameterspezifikation ist optional, wenn der formale Parameter optional ist.

Die Namen formaler Parameter werden beim Überprüfen der Funktionstypkonformität nicht berücksichtigt.

Wenn Sie einen Parameter als optional implizit angeben, lässt der Typ NULL-Werte zu. Die folgenden erstellen identische Funktionstypen:

type function (optional x as text) as any
type function (optional x as nullable text) as any

Tabellentypen

Tabellentypwerte werden verwendet, um die Struktur von Tabellenwerten zu definieren.

Tabellentyp:
      tablerow-type
row-type:

      [field-specification-listopt]

Das Ergebnis der Auswertung eines Tabellentyps ist ein Typwert, dessen Basistyp table ist.

Der Zeilentyp einer Tabelle gibt die Spaltennamen und -typen der Tabelle als geschlossener Datensatztyp an. Damit alle Tabellenwerte mit dem Typ table konform sind, ist der Zeilentyp vom Typ record (der leere offene Datensatztyp). Folglich ist der Tabellentyp abstrakt, da kein Tabellenwert über den Zeilentyp des Typs table verfügen kann (aber alle Tabellenwerte verfügen über einen Zeilentyp, der mit dem Zeilentyp des Typs table kompatibel ist). Das folgende Beispiel zeigt die Erstellung eines Tabellentyps:

type table [A = text, B = number, C = binary] 
// a table type with three columns named A, B, and C 
// of column types text, number, and binary, respectively

Ein Tabellentypwert enthält auch die Definition der Schlüssel eines Tabellenwerts. Ein Schlüssel ist eine Gruppe von Spaltennamen. Es kann maximal ein Schlüssel als Primärschlüssel der Tabelle festgelegt werden. (Innerhalb von M haben Tabellenschlüssel keine semantische Bedeutung. Es ist jedoch üblich, dass von externen Datenquellen, z. B. Datenbanken oder OData-Feeds, Schlüssel für Tabellen definiert werden. Power Query verwendet Schlüsselinformationen, um die Leistung erweiterter Funktionen zu verbessern, z. B. quellübergreifender Verknüpfungsvorgänge.)

Die Standardbibliotheksfunktionen Type.TableKeys, Type.AddTableKey und Type.ReplaceTableKeys können verwendet werden, um den Schlüssel eines Tabellentyps abzurufen, einen Schlüssel zu einem Tabellentyp hinzuzufügen bzw. alle Schlüssel eines Tabellentyps zu ersetzen.

Type.AddTableKey(tableType, {"A", "B"}, false) 
// add a non-primary key that combines values from columns A and B 
Type.ReplaceTableKeys(tableType, {}) 
// returns type value with all keys removed

Nullable-Typen

Für jeden type T kann mithilfe von Nullable-Typen eine Nullable-Variante abgeleitet werden:

Nullable-Typ:
      nullableTyp

Das Ergebnis ist ein abstrakter Typ, der Werte vom Typ T oder den Wert null zulässt.

42 is nullable number             // true null is
nullable number                   // true

Die Askription von type nullableT wird auf die Askription von type null oder typeT reduziert. (Denken Sie daran, dass Nullable-Typen abstrakt sind und kein Wert direkt vom abstrakten Typ sein kann.)

Value.Type(42 as nullable number)       // type number
Value.Type(null as nullable number)     // type null

Die Standardbibliotheksfunktionen Type.IsNullable und Type.NonNullable können verwendet werden, um einen Typ auf NULL-Zulässigkeit zu überprüfen und die NULL-Zulässigkeit eines Typs zu entfernen.

Es gilt Folgendes (für jeden type T):

  • type T ist kompatibel mit type nullable T.
  • Type.NonNullable(type T) ist kompatibel mit type T.

Folgendes ist jeweils paarweise äquivalent (für jeden type T):

    type nullable any
    any

    Type.NonNullable(type any)
    type anynonnull

    type nullable none
    type null

    Type.NonNullable(type null)
    type none

    type nullable nullable T
    type nullable T

    Type.NonNullable(Type.NonNullable(type T))
    Type.NonNullable(type T)

    Type.NonNullable(type nullable T)
    Type.NonNullable(type T)

    type nullable (Type.NonNullable(type T))
    type nullable T

Zugeordneter Typ eines Werts

Der einem Wert zugeordnete Typ ist der Typ, mit dem der Wert als konform deklariert ist.

Einem Wert kann mithilfe der Bibliotheksfunktion Value.ReplaceType ein Typ zugeordnet werden. Diese Funktion gibt entweder einen neuen Wert mit dem zugeordneten Typ zurück oder löst einen Fehler aus, wenn der neue Typ nicht mit dem Wert kompatibel ist.

Wenn einem Wert ein Typ zugeordnet wird, erfolgt nur eine eingeschränkte Konformitätsprüfung:

  • Der zugeordnete Typ darf nicht abstrakt sein, keine NULL-Werte zulassen und muss mit dem intrinsischen (nativen) primitiven Typ des Werts kompatibel sein.
  • Wenn ein benutzerdefinierter Typ zugeordnet wird, der die Struktur definiert, muss er mit der Struktur des Werts übereinstimmen.
    • Für Datensätze: Der Typ muss geschlossen sein, die gleiche Anzahl von Feldern wie der Wert definieren und darf keine optionalen Felder enthalten. (Die Feldnamen und Feldtypen des Typs ersetzen die dem Datensatz derzeit zugeordneten Werte. Vorhandene Feldwerte werden jedoch nicht mit den neuen Feldtypen abgeglichen.)
    • Für Tabellen: Der Typ muss die gleiche Anzahl von Spalten wie der Wert definieren. (Die Spaltennamen und Spaltentypen des Typs ersetzen die der Tabelle derzeit zugeordneten Werte. Vorhandene Spaltenwerte werden jedoch nicht mit den neuen Spaltentypen abgeglichen.)
    • Für Funktionen: Der Typ muss so viele erforderliche und optionale Parameter wie der Wert definieren. (Die Parameter- und Rückgabeassertionen des Typs sowie seine Parameternamen ersetzen diejenigen, die dem aktuellen Typ des Funktionswerts zugeordnet sind. Die neuen Assertionen haben jedoch keine Auswirkungen auf das tatsächliche Verhalten der Funktion.)
    • Für Listen: Der Wert muss eine Liste sein. (Vorhandene Listenelemente werden jedoch nicht mit dem neuen Elementtyp abgeglichen.)

Bibliotheksfunktionen entscheiden sich möglicherweise auf Grundlage der zugeordneten Typen der Eingabewerte dazu, komplexe Typen zu berechnen und diese den Ergebnissen zuzuordnen.

Der einem Wert zugeordnete Typ kann mithilfe der Bibliotheksfunktion Value.Type abgerufen werden. Beispiel:

Value.Type( Value.ReplaceType( {1}, type {number} ) 
// type {number}

Typäquivalenz und Kompatibilität

Die Typäquivalenz ist in M nicht definiert. Eine M-Implementierung kann optional ihre eigenen Regeln verwenden, um Übereinstimmungsvergleiche zwischen Typwerten durchzuführen. Der Vergleich von zwei Typwerten auf Gleichheit sollte mit true ausgewertet werden, wenn sie von der Implementierung als identisch angesehen werden, andernfalls mit false. In beiden Fällen muss die zurückgegebene Antwort konsistent sein, wenn die gleichen beiden Werte wiederholt verglichen werden. Beachten Sie, dass innerhalb einer bestimmten Implementierung mit dem Vergleich einiger identischer Typwerte (z. B. (type text) = (type text)) möglicherweise true zurückgegeben wird, während andere (z. B. (type [a = text]) = (type [a = text])) nicht verglichen werden.

Die Kompatibilität eines bestimmten Typen mit einem primitiven Nullable-Typ kann mithilfe der Bibliotheksfunktion Type.Is überprüft werden, die einen beliebigen Typwert als erstes und einen primitiven Nullable-Typwert als zweites Argument akzeptiert:

Type.Is(type text, type nullable text)  // true 
Type.Is(type nullable text, type text)  // false 
Type.Is(type number, type text)         // false 
Type.Is(type [a=any], type record)      // true 
Type.Is(type [a=any], type list)        // false

Das Überprüfen der Kompatibilität eines bestimmten Typs mit einem benutzerdefinierten Typ wird in M nicht unterstützt.

Die Standardbibliothek umfasst allerdings eine Reihe von Funktionen zum Extrahieren der definierenden Merkmale von benutzerdefinierten Typen, sodass bestimmte Kompatibilitätstests als M-Ausdrücke implementiert werden können. Im Folgenden sind einige Beispiele aufgeführt. Detaillierte Informationen hierzu finden Sie in der Bibliotheksspezifikation.

Type.ListItem( type {number} ) 
  // type number 
Type.NonNullable( type nullable text ) 
  // type text 
Type.RecordFields( type [A=text, B=time] ) 
  // [ A = [Type = type text, Optional = false], 
  //   B = [Type = type time, Optional = false] ] 
Type.TableRow( type table [X=number, Y=date] ) 
  // type [X = number, Y = date] 
Type.FunctionParameters(
        type function (x as number, optional y as text) as number) 
  // [ x = type number, y = type nullable text ] 
Type.FunctionRequiredParameters(
        type function (x as number, optional y as text) as number) 
  // 1 
Type.FunctionReturn(
        type function (x as number, optional y as text) as number) 
  // type number