Перейти к содержимому

Язык М Power Query. Система типов. Основы

Система типов Power Query помогает понять значения, с которыми мы работаем, описать типы данных, ожидаемых от пользовательских функций, дает доступ к документации (которую может отображать IntelliSense) и предоставляет механизм информирования пользователей о структуре данных, которые могут отображаться в хост-среде, например, для настройки типов столбцов.[1]

Подход M к типам на макроуровне заключается в следующем:

  • Каждое значение имеет свой тип.
  • Тип сам по себе является значением.
  • Типы используются для классификации значений.

Предыдущая заметка     Следующая заметка

Скачать заметку в формате Word или pdf, примеры в формате Excel

Основы системы типов

Каждое значение имеет свой тип

Тип значения возвращается функцией Value.Type:

Листинг 1[2]

Тип сам по себе является значением

Результат функции Value.Type является значением – значением типа. Значение типа аналогично другим значениям: оно может быть сохранено в переменной, обработано функциями, к нему можно применить операторы. Такой тип называется type.

Ряд библиотечных функций возвращают значения типа. Эти значения также могут быть созданы с использованием синтаксиса языка. Хотя мы узнаем больше об этом синтаксисе позже, для начала приведем несколько примеров:

Листинг 2

Если значение типа отображается в интерфейсе, вы увидите краткое описание типа:

Рис. 1. Результат – значение с типом type; чтобы увеличить изображение кликните на нем правой кнопкой мыши и выберите Открыть картинку в новой вкладке

Это лишь текстовое представление результата. Значения типа не является строкой. Интерфейс не гарантирует, что будут отображаться все сведения о типе. Для доступа к полному набору информации о значении типа необходимо использовать библиотечные функции.

Типы используются для классификации значений

Классификация означает описание. Например, использование типа таблицы предполагает получение информации о содержащихся в ней столбцах. Классификация также подразумевает группировку по категориям. Например, механизм запроса классифицирует значение, переданное в качестве параметра функции, как приемлемое или неприемлемое, в зависимости от того, соответствует ли тип значения ожидаемому типу параметра. Если тип значения является приемлемым – значение разрешено передавать. Если тип значения является неприемлемым – выдается сообщение об ошибке.

В следующей функции определяются как тип аргумента, так и тип возвращаемого значения. Движок М будет обеспечивать выполнение этих ожиданий, требуя, чтобы передаваемые и возвращаемые значения соответствовали указанным типам.

Листинг 3

На первый взгляд может показаться, что утверждения as list и as text предполагают, что значение параметра должно иметь тип list, а возвращаемое значение должно иметь тип text. Не совсем. Эти утверждения требуют, чтобы значения были совместимы с указанными типами. Существует разница между значением определенного типа и совместимостью с определенным типом. Проверки совместимости выполняются на уровне обнуляемого примитивного типа (nullable primitive type). Вскоре мы обсудим эти проверки, но прежде разберемся с семейством типов.

Обнуляемые примитивные типы

До сих пор в этой серии заметок мы говорили о следующих типах: binary, date, datetime, datetimezone, duration, function, list, logical, null, number, record, table, text, time, type. Все это обнуляемые примитивные типы, но они не составляют полный набор обнуляемых примитивных типов. Давайте познакомимся с остальными членами семьи.

Тип any

Все типы совместимы с типом any. Все значения совместимы с типом any. Вернемся к функции из листинга 3. Если вы определите параметр без указания типа, параметр по умолчанию будет иметь тип any. Аналогично, если вы опустите указание типа функции, он по умолчанию будет any. Следующие два кода эквивалентны:

Тип any – это абстрактный тип. Никакое значение никогда не может иметь абстрактный тип. Вы можете определить выражение, как имеющее абстрактный тип (например, TypeValue = type any), но вы никогда не увидите значение, содержимое которого имеет абстрактный тип. Ни одно выражение не может создать значение, тип которого равен any. Несмотря на то, что значение не может быть абстрактного типа, абстрактные типы все равно полезны, поскольку они предоставляют способ классификации значений. Тип any имеет свое место. Например, хорошее значение по умолчанию, указывающее, что разрешены значения всех типов.

nullable – тип, допускающий значение null

Что делать, если вы создаете функцию, которая должна обрабатывать значения определенного типа и null? Допустим, вы обрабатываете числа из столбца базы данных, которые могут иметь значение null. Функции типа…

…отлично работает для чисел из столбца, но не будет совместима с null. Вы могли бы вернуться к…

…которая допускает как числа, так и null, но также, к сожалению, допускает любые другие типы, чего бы вам хотелось избежать.

На выручку приходит тип, допускающий значение nullnullable. Возьмите любой тип, дополните его типом nullable, и будет возвращен тип, совместимый как с данным типом, так и с null.

nullable можно добавить к определению типа параметров:

А вот саму функцию нельзя определить, как nullable (но иногда это можно обойти).

Обычно тип, возвращаемый nullable, представляет собой абстрактный тип nullable T, где T – это тип, с которого вы начали. Однако, если вы применяете nullable к типу, который уже совместим с null, наличие nullable игнорируется.

Чтобы превратить nullable тип в его ненулевой аналог, используйте библиотечную функцию:

Если указанный тип уже не допускает нулей, функция вернет тот же тип, что и в аргументе.

Идея перехода от nullable типа к его ненулевому аналогу приводит нас к двум специальным типам. Выше мы ввели nullable вариации для обычных типов. Теперь обсудим типы, не допускающие нулевых значений.

Тип anynonnull

Тип any совместим со всеми типами, включая тип null. Каков его ненулевой аналог? anynonnull. Этот абстрактный тип работает так же, как следует из его названия: он совместим со всеми типами, за исключением тех, которые совместимы с null; любое значение, кроме null, совместимо с этим типом.

В следующем примере параметр функции может иметь любое значение, кроме null.

Тип none

Что такое ненулевой аналог типа null? Представляем странный случай абстрактного типа none! Никакое значение никогда не может иметь тип none или быть совместимым с ним.

Сопряжение типов null и none является единственным исключением из правила nullable, в котором говорилось, что применение nullable возвращает тип, совместимый с данным и null. nullable none возвращает тип null, который несовместим с типом none. Тип none совместим только с самим собой, типом any (потому что все типы совместимы с типом any) и типом anynonnull (потому что все типы, не допускающие null, совместимы с anynonnull).

Если типы используются для описания и классификации значений, и ни одно значение никогда не может иметь тип none, в чем его смысл? Вспомните, что каждое выражение M должно, либо возвращать значение, либо вызывать ошибку. Выражение, которое всегда выдает ошибку, можно описать как возвращающее тип none, потому что оно никогда ничего не возвращает.

А зачем вам выражение, которое всегда выдает ошибки? Как насчет вспомогательной функции, которая выдает сообщения об ошибках?

Функция определена с типом none. Если функция попытается вернуть значение (скажем, кто-то изменит код, но оставит as none), движок М выдаст ошибку, поскольку возвращаемое значение будет несовместимо с типом none.

Есть еще один случай, когда выражение можно рассматривать как возвращающее тип none. Что, если выражению требуется много времени, чтобы вернуть значение… очень много времени… бесконечно много времени? Технически выражение, которое никогда не возвращает значение, также может быть описано как возвращающее тип none. Зачем кому-то писать такое бессмысленное выражение – это другой вопрос … но теоретически это можно сделать.

Внутренний секретный тип action

Наконец, существует специальный секретный (он же внутренний Microsoft) тип action, который нигде не упоминается в официальной спецификации языка и который практически не может быть использован с помощью инструментов Power Query, ориентированных на потребителя. Его описание выходит за рамки темы. Если вам интересно узнать о типе action, рекомендую M Mysteries: The Mysterious Type Action—An M-Internal Means to Write Data Modifications to External Systems.

Вся семья

Итак, полное семейство обнуляемых примитивных типов Power Query: action, any, anynonnull, binary, date, datetime, datetimezone, duration, function, list, logical, none, null, number, record, table, text, time, type. А еще конструкции nullable + T с возможностью обнуления каждого Т (за исключением тех типом, которые само по себе совместимы с null). Из этого семейства типы any, anynonnull, none и nullable T являются абстрактными, так же как типы table, record и function.

Последние три, будучи абстрактными, могут вызвать удивление. Все таблицы совместимы с типом table, все записи с типом record и все функции с типом function. Но типы table, record и function не полностью описывают конкретную таблицу, запись или функцию. Для полного описания требуется пользовательский тип – производный тип, который содержит сведения о столбцах таблицы, полях записи или параметрах функции. Пользовательские типы иногда относятся и к спискам, хотя тип list не является абстрактным.

Теперь, когда вы познакомились со всем семейством обнуляемых примитивных типов, обсудим проверку совместимости.

Проверка совместимости

Если вы хотите определить, совместимо ли значение с типом, вы можете использовать оператор совместимости типов – ключевое слово is. Этот оператор возвращает значение true или false.

Предположим, вы используете правила форматирования для отображения значений различных типов в тексте. Создайте пользовательскую функцию на основе Text.From, которая будет принимать значение и возвращать строку, отформатированную с использованием правила, выбранного на основе типа значения.

В языке М также есть оператор утверждения типа – ключевое слово as. Проверяет, совместимо ли значение с заданным обнуляемым примитивным типом, и возвращает само значение, или ошибку.

Утверждения типа в коде функции также определяются с использованием синтаксиса as, хотя это as немного отличается от оператора утверждения типа as. Значение проверяется на совместимость с типом примитива. Если совместимо, возвращается само значение (в случае оператора совместимости типов) или разрешается (в случае определения параметра функции). Если не совместимо, выдается сообщение об ошибке.

Как оператор совместимости типов, так и оператор утверждения типа требуют, чтобы имя типа было жестко запрограммировано. Если вы хотите использовать динамическое имя типа, воспользуйтесь функциями Value.Is и Value.As.

Вторым аргументом в функциях Value.Is и Value.As допускается только обнуляемый примитивный тип.

Проверку совместимости значения с типом также можно выполнить с помощью функции Type.Is. Функция проверяет, всегда ли тип, указанный в качестве первого аргумента, совместим со вторым аргументом, который должен быть обнуляемым примитивным типом.

Динамическое типизирование

Power Query типизируется динамически. Проверки, совместимо ли значение с заданным типом, выполняются путем просмотра типа значения во время выполнения. Нет понятия переменной, имеющей тип, и приведение типов также не имеет значения – важен тип фактического значения.

Здесь указано, что Inner as any. Но это не влияет на тип значения, хранящегося в Result. Указание as any не изменяет тип значения на any. Это просто гарантирует, что все, что возвращается из Inner, совместимо с any. Тип значения в Result будет типом того, что было возвращено Inner (text, number, null, …). Этот тип может меняться каждый раз при вызове выражения в зависимости от того, что возвращает Inner.

Переменная Result сама по себе не имеет типа. Это просто переменная, которая содержит значение – и это значение имеет тип. Чтобы подчеркнуть тот факт, что тип значения – это тоже значение, рассмотрим код:

Как и прежде, Inner имеет такое же утверждение as any. Outer ожидает значение с типом anynonnull. Тип any несовместим с anynonnull, но это выражение не приводит к проверке двух типов, используемых в утверждениях, на совместимость. Inner as any просто гарантирует, что функция вернет что-то совместимое с any. Outer as anynonnull гарантирует, что переданное значение совместимо с anynonnull. Возможно иметь значение, совместимое с обоими утверждениями, даже если сами типы утверждений несовместимы.

Во время выполнения, если значение, возвращаемое Inner(), не равно null, это выражение будет работать нормально; если это не так, будет выдана ошибка.

Итак, тип значения – это то, что имеет значение. Утверждения типа просто проверяют во время выполнения, что передаваемое через них значение совместимо с указанным типом. Сами переменные не имеют типа.

В следующей заметке

Пока всё было относительно просто. Но прежде чем перейти к пользовательским типам, мы узнаем об украшении типов аннотациями. Они не изменяют тип, а служат для информации. Эти аннотации называют аспектами. В редакторе запросов вы, наверное, видели, что существует четыре типа чисел: десятичные, валюта, целые и проценты. На самом деле это просто тип number, дифференцированный по аспектам. Подробнее об этом в следующей заметке.

[1] Заметка написана на основе статьи Ben Gribaudo. Power Query M Primer (Part 16): Type System I – Basics. Если вы впервые сталкиваетесь с Power Query, рекомендую начать с Марк Мур. Power Query.

[2] Номер листинга соответствует номеру запроса в приложенном Excel файле.

Добавить комментарий

Ваш адрес email не будет опубликован.