While Loop v jazyce M
- Vojtěch Šíma
- May 15
- 6 min read
tl;dr Jazyk Power Query M sice nemá klasický while cyklus, jak ho možná znáš z jiných jazyků, ale má funkci List.Generate(), která ti umožní dosáhnout stejného výsledku. V tomhle článku ti vysvětlím, co while cyklus vlastně je a jak jeho chování napodobit v M.
While Loop v Mku?
Power Query M nemá while, for, ani jiné klasické klíčové slovo pro cyklení, na které můžeš být zvyklý z jiných programovacích prostředí. Ale pokud přecházíš z jazyku, kde tyhle klíčové výrazy používáš, do jednoduššího světa M, nebo pokud prostě rád přemýšlíš tradičním programátorským způsobem, i M ti nabízí možnosti, jak podobnou logiku vyjádřit.
Co je while loop
Pokud tě sem poslali bohové M jazyka a vůbec netušíš, co je to while cyklus, v klidu. While je technika, která řídí průběh a opakovaně vykonává blok kódu na základě logické podmínky. Jinak řečeno: dokud něco platí, něco platí.
Příklad z real lifu z normálního prostředí: představ si, že hraješ s kamošema hru, kdo nejrychleji (v nejméně hodech) hodí šestku. Přesně něco takového je while loop—you házíš, dokud nepadne šestka.
Dokud nepadne na kostce šestka → dál házej.
Příklad z real lifu z IT prostředí: spouštíš dotaz na pomalé SQL databázi. Dotaz opakuješ, dokud celkový čas zpracování nepřekročí nastavený limit.
Dokud čas zpracování < timeout → pokračuj v dotazování.
Jak to napodobím v Mku?
Okáčko, pojďme se podívat, jak to můžeš ukuchtit v Mku. Záleží na tom, čeho přesně chceš dosáhnout, ale existuje několik způsobů, jak na to. Nejblíž klasickému while cyklu má funkce List.Generate(). Tahle funkce má doslova parametr ve stylu while true zabudovaný přímo v sobě.
Pojďme si to rozebrat.
List.Generate()
List.Generate(initial as function, condition as function, next as function, optional selector as nullable function) as list
List.Generate() vezme počáteční hodnotu (může to být cokoliv → číslo, záznam, seznam atd.) a opakovaně ji vrací (nebo aktualizovanou hodnotu v každé iteraci), dokud není splněna podmínka.
Většinou chceš svou počáteční hodnotu v každém kroku upravovat tak, aby se k požadovanému splnění podmínky postupně dostala.
Můžeš ale také využít aktuální hodnotu iterace k jiným účelům—předat ji do jiné funkce, transformovat ji, nebo klidně jen vrátit řetězec, pokud právě to potřebuješ.
Pojďme se teď podívat, jak by v M jazyce vypadal náš příklad s házením kostkou.
List.Generate(
()=> 0,
each _<> 6,
each Number.Round(Number.RandomBetween(1,6), 0)
)
Okomentované:
List.Generate(
()=> 0, //nastav počáteční hodnotu
each _<> 6, // házej dál, dokud nepadne 6
each Number.Round(Number.RandomBetween(1,6), 0) // při každé iteraci vygeneruj nové číslo
)

Když se ale podíváš pozorněji, tenhle kód má pár očividných nedostatků. Za prvé, počáteční hodnota by neměla být nula—první iterace by měla rovnou představovat hod kostkou. Zbytek funguje, ale když padne šestka, podmínka ukončí cyklus ještě předtím, než se ta poslední šestka přidá do seznamu, což může být trochu matoucí. Nakonec bychom mohli přidat i čítač, abychom sledovali, kolik hodů to vlastně trvalo, a místo vracení celého seznamu jen jednoduše vrátit tuhle informaci jako text.
Pojďme se podívat, jestli si List.Generate() poradí s našimi vylepšeními:
let
rollDice = ()=> Number.Round(Number.RandomBetween(1,6), 0),
throwDice =
List.Generate(
()=> rollDice(),
each _ <> 6,
each rollDice()
)
in
throwDice
Uvnitř List.Generate() jsme toho vlastně moc neměnili. Jediné, co jsme udělali, bylo zavedení proměnné—nebo spíš funkce—která se stará o hod kostkou, takže nemusíme stejný zápis psát dvakrát v rámci List.Generate(). Zbytek zůstal beze změny.
Tím už máme i čítač vyřešený. Není potřeba přidávat žádný záznam na sledování počtu hodů, protože počet hodů bude vždy odpovídat počtu položek v seznamu plus jedna (cyklus totiž končí, když padne šestka, ale ten poslední hod už do seznamu nepřidá).
List.Generate() vždycky vrátí seznam (nebo chybu). Pokud chceš místo toho vrátit jen jednoduchý text nebo řetězec, musíš výsledek zabalit do jiné funkce.
let
rollDice = ()=> Number.Round(Number.RandomBetween(1,6), 0),
throwDice =
List.Generate(
()=> rollDice(),
each _ <> 6,
each rollDice()
),
resultText = "It took you " & Text.From( List.Count( throwDice) + 1 ) & " throw/s to roll number six!"
in
resultText

Jasně, šlo by to celé ošetřit podmínkou přímo při „throw“, ale věřím ti, že si s touhle částí už poradíš sám.
Omezení
Technicky vzato List.Generate() nemá pevně daný limit počtu iterací. Skutečný problém je spotřeba paměti. Kdybys měl nekonečně RAM, mohl bys teoreticky spustit nekonečný cyklus a nechat ho běžet navždy.
To ale rozhodně nedoporučuju—hlavně ne v Power BI Service, kde bys velmi rychle vyčerpal kapacitu. Pokud si chceš jen pohrát a vyzkoušet, jak to funguje, dělej to radši lokálně, kde v nejhorším padne jen tvoje RAM.
Já sám jsem testoval, kolik paměti by zabral nekonečný cyklus, který neustále vrací slovo „kitten“ nebo generuje nekonečný seznam čísel. 1 miliarda řádků zabrala jen pár minut a spolkla zhruba 40 GB paměti. Bez sebemenšího zaváhání.

Když tenhle kód přeložíš do běžné řeči, znamená to v podstatě: Dokud pravda je pravda → vracej „kitten“.
Smyčky řízené daty skrz List.Generate()
Smyčka řízená daty (neoficiální termín, prosím necituj mě) je taková, která se spoléhá na externí zdroj, aby vrátila další data. V našem příkladu s házením kostkou jsme jen počítali počet iterací, ale v reálném světě musíš často uvnitř List.Generate() provést několik transformací, než se vůbec přiblížíš k podmínce pro ukončení cyklu.
Příklady takových situací můžou být:
API, které vrací odkaz na další stránku a ty ho musíš zavolat, abys získal další data.
Scénář stahování denních souborů, kdy opakovaně načítáš a sbíráš soubory, dokud jejich celková velikost nedosáhne stanoveného limitu.
Příklad #1
Začněme klasickým případem použití: načítání dat z REST API s stránkováním pomocí kurzoru. V tomhle příkladu půjde o Power BI REST API Get Activities.
Pokud chceš podrobnější vysvětlení (včetně všech dílčích částí), mrkni na můj jiný článek, kde jsem tenhle postup vysvětlil na příkladu stránkování s tokenem.
Tady si to zjednoduším a ukážu ti jen část s while logikou. Vlastní funkce jako request a nextPageRequest už máme nadefinované předem. Pojďme se podívat na samotný cyklus.
Pro přehlednost: funkce nejsou volány klasicky přes klíčové slovo each, ale definujeme je pomocí parametru i, který drží záznam aktuální iterace.
Všechno řešíme přes List.Generate(). Jako první parametr nastavujeme počáteční požadavek. Ten vrátí data z první stránky a pokud existuje další stránka, vrátí odkaz na pokračování v poli continuationUri. Iteraci řídíme záznamem s poli request, next a isLast.
Druhý parametr kontroluje, jestli aktuální odpověď není poslední stránka.
Třetí parametr určuje, co se stane v další iteraci. Tady chceme vytvořit stejný záznam, jen s jedním krokem zpoždění. To je potřeba, protože jsme si nastavili podmínku (tu „while“ část), aby kontrolovala pole lastResultSet. Pokud je na poslední stránce nastaveno na TRUE, cyklus skončí dřív, než by načetl data z poslední stránky.
Poslední, nepovinný čtvrtý parametr—takzvaný selector—pak jen vezme aktuální data z požadavku, konkrétně z pole activityEventEntities.
List.Generate(
()=>
[
request = request(#date(2025,5,11), #date(2025,5,11)),
next = request[continuationUri]?,
isLast = request[lastResultSet]?
]
,
(i)=> not i[isLast]?,
(i)=>
[
request = nextPageRequest(i[request]?[continuationUri]?),
next = i[request][continuationUri]?,
isLast = i[request][lastResultSet]?
],
(i)=> i[request][activityEventEntities]
)
Pokud chceš, můžeš pole request ve třetím parametru jednoduše nadefinovat takhle: request = nextPageRequest(next), což udělá úplně to samé. Já jsem ho ale radši nastavil takto, protože mi to přijde přehlednější a srozumitelnější.
Příklad #2
Představ si, že si stavíš report, který má sledovat, které soubory nejvíc přispívají k dosažení denního limitu velikosti souborů. Dejme tomu 500 MB. Máš podezření, že velké soubory nahrané hned ráno zbytečně zahlcují systém a zpomalují produkční prostředí.
Zdroj dat je API, které každý den sbírá metadata souborů. Data máš seřazená a snažíš se zjistit, které soubory tě dovedou těsně pod hranici 500 MB. Pokud by další soubor limit překročil, už ho tam nechceš. Cílem je zachytit jen ty soubory, které se bezpečně vejdou do nastaveného limitu, a dál je analyzovat.
Poďme se mrknou, jak něco takového vyrobíš pomocí List.Generate().
resultWithSelector = (filesList as list, fileSizeField as text, sizeCap as number)=>
List.Generate(
() =>
[
index = 0,
item = filesList{index},
runningMB = Record.Field(item, fileSizeField)
],
each [runningMB] <= sizeCap,
each
[
index = [index]+1,
item = filesList{index},
runningMB = [runningMB]+Record.Field(item, fileSizeField)
],
each [item]
)
Funkci resultWithSelector jsme navrhli jako univerzální nástroj, který si můžeš snadno upravit podle svých konkrétních potřeb. Má tři klíčové parametry, kterými ovládáš její chování:
filesList (list): představuje tvůj seznam souborů.
fileSizeField (text): určuje název pole v záznamu souboru, které obsahuje jeho velikost. Pomocí něj se kontroluje dosažení limitu.
sizeCap (number): udává maximální povolenou celkovou velikost. Smyčka se zastaví, jakmile součet velikostí dosáhne nebo překročí tento limit.
Funkci můžeš zavolat například takto:
result = resultWithSelector(filesForDay, "SizeMB", 500)
Samozřejmě konkrétní podoba bude záviset na tom, jak máš nastavené API. V našem příkladu předpokládáme, že soubory jsou už seřazené a uložené v seznamu (filesForDay), takže k nim můžeme přistupovat pomocí indexu. Díky tomu můžeme seznam projít postupně: v každém kroku vezmeme další soubor a jeho velikost přičteme k průběžnému součtu (runningMB).
Proces pokračuje, dokud celková velikost nedosáhne nebo nepřekročí stanovený limit 500 MB. Výsledkem je přehledný seznam záznamů, kde každý záznam představuje jeden soubor, který přispěl k dosažení stanoveného limitu.
Výsledkem je pěkný seznam záznamů, kde každá položka odpovídá jednomu souboru.

Shrnutí
V dnešním článku jsme si ukázali, jak v M jazyce napodobit klasický while cyklus. I když M samotné klíčové slovo while nemá, nabízí funkci List.Generate(), která se dá jako while cyklus bez problémů využít. Smyčka postavená pomocí List.Generate() ti pomůže jak u jednoduchých případů, jako byl náš příklad s házením kostkou, tak i u praktičtějších scénářů z praxe—například při stránkování dat z API nebo postupném načítání souborů až do dosažení určitého limitu.
Samozřejmě tahle funkce toho umí víc než jen simulovat while cykly—například i klasické for smyčky—ale to už je téma na jiný článek. Díky, že jsi dočetl až sem!
Commentaires