(Variadics)完整指南:從參數(shù)列表、VariadicList 到 VariadicPack 與關(guān)鍵字參數(shù))
Mojo 可變參數(shù)Variadics完整指南從參數(shù)列表、VariadicList 到 VariadicPack 與關(guān)鍵字參數(shù)【免費(fèi)下載鏈接】mojoThe Modular Platform (includes MAX Mojo)項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mo/mojo本指南以 Mojo/proposals/variadics-design.md 為骨架系統(tǒng)講解 MojoModular 平臺(tái)中可變參數(shù)特性的設(shè)計(jì)、語法與底層實(shí)現(xiàn)編譯期可變參數(shù)列表TypeList/ParameterList、運(yùn)行時(shí)同構(gòu)可變參數(shù)VariadicList、異構(gòu)可變參數(shù)包VariadicPack以及可變關(guān)鍵字參數(shù)StringDict設(shè)計(jì)文檔中寫作OwnedKwargsDict。讀完本文你將掌握如何在函數(shù)簽名中使用*args、*args: *Ts與**kwargs理解其借用/所有權(quán)語義、低層 lowering 方式并能結(jié)合標(biāo)準(zhǔn)庫源碼與測(cè)試用例寫出慣用的元編程與轉(zhuǎn)發(fā)代碼。概述Mojo 可變參數(shù)的四大形態(tài)Mojo 的可變參數(shù)variadic能力橫跨編譯期與運(yùn)行期兩條主線形態(tài)語法示例背后類型作用域編譯期類型列表*Ts: AnyTypeTypeListdef/struct/comptime參數(shù)位編譯期值列表*elts: IntParameterList同上運(yùn)行時(shí)同構(gòu)參數(shù)*args: IntVariadicList運(yùn)行時(shí)調(diào)用點(diǎn)運(yùn)行時(shí)異構(gòu)參數(shù)包*args: *TsVariadicPack運(yùn)行時(shí)調(diào)用點(diǎn)運(yùn)行時(shí)關(guān)鍵字參數(shù)**kwargs: IntStringDict[V]文檔中的OwnedKwargsDict[V]運(yùn)行時(shí)調(diào)用點(diǎn)這些特性統(tǒng)一采用 Python 風(fēng)格的語法*args與**kwargs對(duì)熟悉 Python 的用戶有天然的親切感。編譯期參數(shù)列表把靜態(tài)已知數(shù)量的元素打包進(jìn)一個(gè)綁定運(yùn)行時(shí)同構(gòu)參數(shù)允許單個(gè)參數(shù)位接收任意數(shù)量的同形狀實(shí)參異構(gòu)參數(shù)包用*args: *Ts語法把一組可變參數(shù)與一組可變類型一一對(duì)應(yīng)關(guān)鍵字參數(shù)則收集未被命名參數(shù)消費(fèi)的keyvalue操作數(shù)。本文檔當(dāng)前狀態(tài)為 Draft是面向語言/標(biāo)準(zhǔn)庫貢獻(xiàn)者與進(jìn)階用戶的實(shí)現(xiàn)級(jí)參考。本文所有示例均可對(duì)照標(biāo)準(zhǔn)庫測(cè)試 Mojo/stdlib/test/builtin/test_variadic.mojo該文件是語法最新真相的來源與核心實(shí)現(xiàn) Mojo/stdlib/std/builtin/variadics.mojo。Variadic parameter lists編譯期參數(shù)列表可變參數(shù)parameter列表出現(xiàn)在編譯期參數(shù)位置def、struct、comptime等聲明上把靜態(tài)已知數(shù)量的元素捆綁進(jìn)一個(gè)綁定。與 Python 相同Mojo 用前導(dǎo)*表示一組值或類型def takes_types[*Ts: AnyType](): ... def takes_values[*elts: Int](): ...Mojo 嚴(yán)格區(qū)分這兩種情況標(biāo)準(zhǔn)庫分別以TypeList與ParameterList呈現(xiàn)均定義在 Mojo/stdlib/std/builtin/variadics.mojo。兩者建立在同一個(gè) KGEN 概念之上一個(gè) MLIR!kgen.param_list值其元素共享同一種編譯期形態(tài)要么全部滿足某個(gè) trait 的類型要么全部是同一種類型的值。它們是內(nèi)建類型正常使用無需顯式導(dǎo)入——模塊頭部注釋即聲明These are Mojo built-ins, so you dont need to import them.TypeList類型序列的編譯期操作類型列表綁定一系列類型聲明時(shí)使用 trait 約束而非值類型例如*Ts: AnyType或*Ts: Movable。編譯器通過TypeList操作將其暴露出來對(duì)上面的示例type_of(Ts)就是TypeList。從源碼看TypeList底層類型正是_MLIR.KGENParamListType[Self.Trait]即!kgen.param_listTrait見 variadics.mojo并借助#kgen.param_list.size、#kgen.param_list.get等 MLIR 屬性實(shí)現(xiàn)查詢與索引。TypeList提供一系列實(shí)用操作編譯期size/length元素個(gè)數(shù)如tl.length定長索引Ts[i]可用于comptime循環(huán)通過__getitem_param__[idx]實(shí)現(xiàn)構(gòu)造與變換助手TypeList.of、splat、tabulate、map、reduce、filter_idx、contains、reversed、slice等。典型用途包括對(duì)多個(gè)類型參數(shù)做 trait 謂詞判斷參見 Mojo/stdlib/std/traits/movable.mojo以及遍歷/變換類型包的元編程。測(cè)試用例 test_variadic.mojo 中的test_type_list_map_to_values、test_type_list_filter_idx_*、test_type_list_reduce_idx等覆蓋了這些操作的常見組合。ParameterList同類型編譯期值序列值列表綁定一系列共享同一類型T的編譯期值例如*args: Int或*names: String。它變成一個(gè)元素類型為T的ParameterList。可以編譯期迭代或索引ParameterList.get_span()在需要指針線性布局時(shí)把元素物化為連續(xù)常量數(shù)組背后的Span——源碼顯示它通過#pop.variadic_to_array把param_list展平成扁平數(shù)組、再映射為運(yùn)行期常量并取首元素指針見 variadics.mojo。ParameterList.of、splat等構(gòu)造器與TypeList的故事一一對(duì)應(yīng)。兩個(gè)類型刻意保持平行設(shè)計(jì)文檔注釋表示未來在 Mojo metatype 故事成熟后有望統(tǒng)一TypeList.map_to_values把每個(gè)元素類型經(jīng)編譯期生成器映射為同構(gòu)值的ParameterList——類型包驅(qū)動(dòng)值包是常見模式。測(cè)試 test_variadic.mojo 的test_type_list_map_to_values正是此用法。需要注意的是與參數(shù)列表不同參數(shù)列表目前不支持異構(gòu)列表或關(guān)鍵字參數(shù)列表文檔明確表示若未來有足夠需求支撐其復(fù)雜度可能加入。Homogeneous variadic arguments運(yùn)行時(shí)同構(gòu)可變參數(shù)上一節(jié)討論的是聲明上的參數(shù)本節(jié)討論運(yùn)行時(shí)調(diào)用點(diǎn)的實(shí)參一個(gè)參數(shù)位接受任意數(shù)量的實(shí)參但所有實(shí)參必須是同一類型T。表面語法同樣是前導(dǎo)*但現(xiàn)在它命名的是一個(gè)VariadicList而非ParameterList。簽名與基本用法同構(gòu)可變參數(shù)形如*args: Int或*parts: String。在 callee 內(nèi)部args可以像一個(gè)小型順序集合使用len(args)、args[i]、for x in args當(dāng)T滿足Writable時(shí)還有args.write_to(s)之類助手來渲染元組形態(tài)def print_ints(*values: Int): for i in range(len(values)): print(values[i]) def main(): print_ints(10, 20, 30) # 三個(gè)獨(dú)立實(shí)參一個(gè)可變參數(shù)綁定只關(guān)心值不關(guān)心索引時(shí)迭代器路徑同樣自然def sum_ints(*values: Int) - Int: var total 0 for v in values: total v return totalcallee 收到的是VariadicList*args: T語法映射到標(biāo)準(zhǔn)庫類型VariadicListvariadics.mojo。它的內(nèi)部字段是var _value: Span[Self._EltPointerType, ImmUntrackedOrigin] # 元素指針的 Span也就是說它攜帶的是一個(gè)指向各實(shí)參引用的指針的Span而不是T值的密集數(shù)組。這種布局讓 callee 能以args[idx]暴露正確的借用或移動(dòng)語義__getitem__通過self._value.unsafe_ptr()[unsafe_offsetidx][]解引用同時(shí)讓膠水對(duì)象小到足以按值傳遞還允許非Movable的值通過可變參數(shù)傳遞。文檔給出的粗略 lowering 圖景僅為示意名非精確 MLIR// Caller: foo(a, b, c) with def foo(*xs: T) 1. 編譯器照常為每個(gè)實(shí)參分配存儲(chǔ)棧槽、寄存器或 ABI 要求的形式。 2. 構(gòu)造一個(gè)臨時(shí)數(shù)組其第 i 項(xiàng)是指向第 i 個(gè)實(shí)參的 pointer-to-ref 所有元素的指針/引用 MLIR 類型相同。 3. 調(diào)用 foo傳入一個(gè) VariadicList其內(nèi)部 Span 指向該數(shù)組長度為實(shí)參個(gè)數(shù)。 4. VariadicList.__init__由該數(shù)組隱式構(gòu)造把 POP 數(shù)組轉(zhuǎn)成元素指針的 Span __getitem__ 通過該 span 索引并加載引用。源碼佐證了第 4 步隱式__init__[size, container_origin]接收編譯器生成的元素指針數(shù)組引用用pop.array.gep取首元素指針后unsafe_bitcast為元素指針類型再包成Span(unsafe_ptr..., lengthsize)見 variadics.mojo。所以可變參數(shù)束永遠(yuǎn)是指針的 span——即使T是 trivial 類型也是如此。閱讀性能文檔時(shí)需記住可變參數(shù)在 caller 側(cè)仍是獨(dú)立對(duì)象列表只是一層用于統(tǒng)一索引的間接層。借用與擁有實(shí)參默認(rèn)情況下*args: T借用每個(gè)元素。當(dāng)需要取得所有權(quán)時(shí)例如T是線性類型或想用consume_elements移出使用var *args: T。VariadicList用is_owned參數(shù)跟蹤該狀態(tài)置位時(shí)__deinit__反向遍歷列表、逐個(gè)銷毀元素與常規(guī)實(shí)參析構(gòu)順序一致見 variadics.mojo__parameter def destroy_elem(_idx: Int, var arg: ExplicitDelOnly): arg^.destroy() def take_owned_linear(var *args: ExplicitDelOnly): args^.consume_elements[destroy_elem]() # Caller 傳臨時(shí)對(duì)象callee 逐個(gè)消費(fèi)。 take_owned_linear(ExplicitDelOnly(5), ExplicitDelOnly(10))該示例直接取自 test_variadic.mojo 的test_variadic_list_linear_type。consume_elements調(diào)用處的^選擇可變參數(shù)束的 owned 視圖。consume_elements與想要var元素的 API許多下沉一串值的 API 與List列表字面量構(gòu)造器形狀一致var *values: Self.T加上values^.consume_elements[...]把每個(gè)實(shí)參移入新分配的存儲(chǔ)。從實(shí)現(xiàn)看consume_elements(deinit self, elt_handler)只在Self.is_owned時(shí)可用where約束即報(bào)錯(cuò)信息consume_elements may only be called on owned variadic lists它用__get_address_as_owned_value逐個(gè)轉(zhuǎn)移所有權(quán)給處理器閉包見 variadics.mojo。值得注意的是源碼注釋這里刻意不用Pointer.unsafe_take_pointee因?yàn)樗笤厥荕ovable而VariadicList明確不需要這一前提。打印與調(diào)試當(dāng)element_type滿足Writable時(shí)VariadicList實(shí)現(xiàn)write_to/write_repr_to內(nèi)部_write_elements循環(huán)拼接(a, b, c)形態(tài)is_reprTrue時(shí)逐元素調(diào)用write_repr_towrite_repr_to還會(huì)套上VariadicList[Int]類型名見 variadics.mojo。這就是為什么測(cè)試期望write_to輸出(1, 2, 3)、write_repr_to輸出VariadicListInt, Int(2), Int(3)))。測(cè)試用例見test_variadic_list_write_to與test_variadic_list_write_repr_totest_variadic.mojo。Variadic packs異構(gòu)可變參數(shù)包可變參數(shù)包variadic pack是同構(gòu)VariadicList的異構(gòu)對(duì)應(yīng)物。不再用*args: T單一靜態(tài)元素類型而是把類型參數(shù)包與實(shí)參包配對(duì)實(shí)參類型取自該包def callee*Ts: Writable raises: ...callee 收到一個(gè)VariadicPack定義于 variadics.mojo。與VariadicList一樣它是RegisterPassable且參與所有權(quán)is_owned、var *args: *Ts、consume_elements、__del__但內(nèi)部表示是類似Tuple的異構(gòu)值其底層 MLIR 類型是!lit.ref.pack:param_listTrait ... isParamPack形態(tài)的!kgen.struct見 variadics.mojo每個(gè)槽位可能對(duì)應(yīng)不同大小、不同 ABI 的具體類型。為什么包需要comptime for每個(gè)實(shí)參槽位可能是不同的具體類型大小與 ABI 各異。因此包更接近元組而非數(shù)組不存在單一的T可用于args[runtime_idx]。索引通過__getitem_param__[index]暴露使用編譯期索引實(shí)現(xiàn)中用lit.ref.pack.extract抽取槽位編譯器才能為每個(gè)位置生成正確的加載指令。這正是慣用代碼用comptime for而非運(yùn)行期for i in range(len(args))遍歷包的原因def count_many_things*ArgTypes: Intable - Int: var total 0 comptime for i in range(args.__len__()): # 每個(gè) args[i] 都有來自 *ArgTypes 的不同具體類型。 total Int(args[i]) return total def main(): print(count_many_things(Int8(5), UInt32(11), Int(12))) # 28該示例改編自VariadicPack的 docstringvariadics.mojo。關(guān)鍵點(diǎn)循環(huán)變量i是編譯期參數(shù)因此每個(gè)args[i]都單獨(dú) monomorphize。VariadicPack.__len__直接返回Self.Ts.length類型包長度編譯期已知。Writable包與轉(zhuǎn)發(fā)當(dāng)每個(gè)元素類型都滿足Writable時(shí)包實(shí)現(xiàn)write_to及相關(guān)操作def helper*Ts: Writable raises: var s String() args.write_to(s) # 對(duì) (1, hello, True) - (1, hello, True) def forwarder*Ts: Writable raises: helper(*args) # splat 原樣轉(zhuǎn)發(fā)包 # Caller: forwarder(1, hello, True)轉(zhuǎn)發(fā)模式callee(*args)在test_variadic_pack_forwarding、單元素變體test_variadic_pack_forwarding_single_element、空包變體test_variadic_pack_forwarding_empty以及多跳版本test_variadic_pack_forwarding_through_two_levels中均有覆蓋test_variadic.mojo。空包與單元素包的轉(zhuǎn)發(fā)方式完全相同對(duì)應(yīng)測(cè)試中的forwarder()與forwarder(42)。SomeTypeList語法糖當(dāng)只需要任意數(shù)量的類型且每個(gè)都滿足Trait時(shí)可以不引入顯式*Ts綁定直接在def上命名實(shí)參包def foo(*args: *SomeTypeList[Writable]) raises: var s String() args.write_to(s)SomeTypeList是定義于 Mojo/stdlib/std/builtin/anytype.mojo 的編譯期別名把Some[T: Trait]的思想擴(kuò)展到受同一 trait 約束的整個(gè)TypeList在可變調(diào)用點(diǎn)尤其有用。對(duì)應(yīng)測(cè)試為test_variadic_pack_sometest_variadic.mojo。與Tuple的關(guān)系Tuple是擁有異構(gòu)序列的典型結(jié)構(gòu)體。其構(gòu)造函數(shù)接收一個(gè)與其元素類型列表對(duì)齊的可變參數(shù)包# 概念示意見 Mojo/stdlib/std/builtin/tuple.mojo struct Tuple*element_types: Movable: def __init__(out self, var *args: *Self.element_types): ...也就是說VariadicPack本質(zhì)上就是運(yùn)行期被降級(jí)為結(jié)構(gòu)體的異構(gòu)元組編譯器在編譯期掌握每個(gè)元素的精確類型以生成正確的內(nèi)存布局與訪問代碼。Variadic keyword arguments可變關(guān)鍵字參數(shù)可變關(guān)鍵字參數(shù)是 Python**kwargs的運(yùn)行時(shí)對(duì)應(yīng)物。callee 可以接受任意數(shù)量的額外關(guān)鍵字實(shí)參且這些實(shí)參的值共享同一個(gè)類型V。鍵始終是運(yùn)行時(shí)String調(diào)用點(diǎn)寫下的關(guān)鍵字名。與可變參數(shù)包不同這里沒有異構(gòu)值類型列表**kwargs: Int意味著每個(gè)傳入值都必須是Int而非混合類型元組。表面語法是參數(shù)名前導(dǎo)**且必須位于簽名末尾、其他參數(shù)之后def variadic_kwargs(a: Int, b: Int, *args: Int, c: Int, d: Int, **kwargs: Int): pass def print_nicely(**kwargs: Int): for item in kwargs.items(): print(item.key, , item.value) def main(): print_nicely(a7, y8)每個(gè)函數(shù)只允許一個(gè)**參數(shù)且必須帶類型注解例如**kwargs: Int不允許裸寫**kwargs。解析器側(cè)的支持可見于 Mojo/test/mojo-parser/decls/variadic_kwargs.mojo 與 Mojo/lib/MojoParser/Signatures.cpp后者負(fù)責(zé)簽名中kw_vararg槽位的解析。callee 收到的是StringDict文檔中的OwnedKwargsDict設(shè)計(jì)文檔寫作時(shí)**kwargs: V以var kwargs: OwnedKwargsDict[V]傳入。當(dāng)前倉庫中該類型已更名為StringDict[V]定義于 Mojo/stdlib/std/collections/dict.mojo其 docstring 明確寫著用于向函數(shù)傳遞擁有的可變關(guān)鍵字實(shí)參的容器且用戶不應(yīng)直接實(shí)例化它——編譯器在調(diào)用點(diǎn)構(gòu)建、傳入 callee。這一更名在 Mojo/docs/site/releases/v1.0.0.md 的發(fā)布說明中有記錄OwnedKwargsDict→StringDict。StringDict內(nèi)部包裝了一個(gè)Dict[String, V, default_comp_time_hasher]對(duì)外暴露字典風(fēng)格接口len、in、__getitem__支持按String或ImmStringSpan查找后者免分配、__setitem__、keys()、values()、items()、find、pop等另有deinit_with用于值非Deinitable時(shí)的顯式析構(gòu)。關(guān)鍵字可變參數(shù)以擁有var方式傳遞因?yàn)樽值渫ǔ閱蝹€(gè)調(diào)用點(diǎn)構(gòu)建。不能顯式寫read或mut約定# 暫不支持。 def borrowed_kwargs(mut **kwargs: Int): ...在 callee 內(nèi)部kwargs擁有該字典及其插入的值這與調(diào)用 lowering 用_insert把每個(gè)關(guān)鍵字操作數(shù)轉(zhuǎn)入字典的方式一致。調(diào)用 lowering文檔給出的粗略 lowering 圖景僅為示意名非精確 MLIR// Caller: foo(x9, stuff8) with def foo(**kwargs: Int) 1. 分配一個(gè)空的 OwnedKwargsDict[Int]局部臨時(shí)對(duì)象。 2. 對(duì)調(diào)用點(diǎn)每個(gè)關(guān)鍵字操作數(shù) - 把鍵物化為編譯期 String 字面量。 - 求值值表達(dá)式。 - 調(diào)用 OwnedKwargsDict::_insert(dict, key, value)把值的所有權(quán)轉(zhuǎn)入字典。 3. 把填好的字典作為擁有的 **kwargs 實(shí)參傳給 foo。重載解析收集所有未綁定到前面具名參數(shù)的關(guān)鍵字操作數(shù)路由到kw_vararg槽位若 callee 沒有**kwargs參數(shù)這些操作數(shù)即報(bào)錯(cuò)。位置參數(shù)*args與**kwargs可出現(xiàn)在同一簽名中各自吃掉自己種類的操作數(shù)。用**kwargs^轉(zhuǎn)發(fā)要把整個(gè)關(guān)鍵字束轉(zhuǎn)發(fā)給另一個(gè)可變關(guān)鍵字 callee使用雙星解包形式并配合^轉(zhuǎn)移所有權(quán)def pass_kwargs(**kwargs: Int): takes_int_variadic_kwargs_multiline(**kwargs^)不加^時(shí)轉(zhuǎn)發(fā)會(huì)嘗試拷貝字典并因StringDict不可隱式拷貝而失敗加上^后caller 的字典被移入內(nèi)層調(diào)用。這是callee(*args)splat 可變參數(shù)包的關(guān)鍵字對(duì)應(yīng)物。當(dāng)前尚不支持從普通Dict[String, V]解包例如print_nicely(**my_dict)今天只有從另一個(gè)**kwargs綁定轉(zhuǎn)發(fā)可用。泛型推斷當(dāng)函數(shù)在值類型上泛型時(shí)關(guān)鍵字實(shí)參可以像位置實(shí)參一樣驅(qū)動(dòng)類型推斷def infers_from_kwargsT: SomeTrait: pass # T 由關(guān)鍵字值推斷為 MemOnly。 infers_from_kwargs(yMemOnly(), zs)推斷出的元素類型必須滿足聲明的 trait 約束且兼容轉(zhuǎn)入StringDict._insertowned、movable 值。測(cè)試 Mojo/stdlib/test/collections/test_dict.mojo 的test_owned_kwargs_dict與第 1944 行的test_owned_kwargs_dict_linear覆蓋了可變關(guān)鍵字參數(shù)在 callee 內(nèi)部暴露的字典 API 表面以及線性值非Copyable的轉(zhuǎn)移規(guī)則。當(dāng)前限制以下缺口是首個(gè)版本的有意取舍亦可參見 Mojo 手冊(cè) functions 章節(jié)僅支持同構(gòu)值所有關(guān)鍵字共享一個(gè)值類型V鍵始終是String沒有類型化鍵變體始終 owned不支持read **kwargs或mut **kwargs調(diào)用點(diǎn)不支持從普通Dict解包不支持編譯期可變關(guān)鍵字參數(shù)聲明上的**kwparams: ...值必須是Movable并滿足StringDict的約束非擁有的線性值遵循與其他 owned 調(diào)用實(shí)參相同的轉(zhuǎn)移規(guī)則。實(shí)踐建議與測(cè)試參照選擇形態(tài)只需要同類型的一串值時(shí)用*args: TVariadicList需要每個(gè)槽位類型不同、類型由 trait 約束時(shí)用*args: *TsVariadicPack并務(wù)必用comptime for遍歷需要收集任意關(guān)鍵字鍵值對(duì)時(shí)用**kwargs: VStringDict[V]。所有權(quán)要消費(fèi)移動(dòng)實(shí)參一律寫成var *args: T/var *args: *Ts再經(jīng)consume_elements或**kwargs^轉(zhuǎn)移借用場景保持默認(rèn)read語義。元編程TypeList的map/reduce/filter_idx/contains與ParameterList.get_span()是編譯期類型/值計(jì)算的主力工具SomeTypeList[Trait]是可變調(diào)用點(diǎn)上約束類型包的最簡寫法。驗(yàn)證語法任何語法細(xì)節(jié)以測(cè)試文件 Mojo/stdlib/test/builtin/test_variadic.mojo 為準(zhǔn)其中test_variadic_*、test_type_list_*、test_parameter_list_*、test_variadic_pack_*、test_dynamic_variadic_pack等用例分別覆蓋了本文討論的各條路徑關(guān)鍵字字典相關(guān)驗(yàn)證在 Mojo/stdlib/test/collections/test_dict.mojo。文檔仍標(biāo)注為 DraftTypeList/ParameterList的統(tǒng)一、**kwparams編譯期參數(shù)等能力屬于未來演進(jìn)方向?qū)嶋H能力請(qǐng)以當(dāng)前倉庫代碼與測(cè)試為準(zhǔn)。【免費(fèi)下載鏈接】mojoThe Modular Platform (includes MAX Mojo)項(xiàng)目地址: https://gitcode.com/GitHub_Trending/mo/mojo創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考