Mojo —— 版本更新 v24.4
文章目录
前言
Mojo
v24.4
于 2024-06-07
更新,主要更新了
- 改进了
def
的性能并更容易使用 - 继续围绕
UnsafePointer
类型统一标准库API
- 改进了许多集合类型
- 改进了
Dict
的插入性能 - 新的
@parameter for
替代了早期(不太可靠的)@unroll
装饰器
下面我们介绍一下具体做了哪些更新
语言层面
函数
def 函数更新
之前的版本中,def
默认使用 owned
方式传递参数,这会导致不必要的拷贝并造成性能损耗。
所以在这个版本中,def
的默认行为变为 borrowed
(借用),但是,只有当值是可变的时候会对值进行局部拷贝。
现在,你可以将 def 函数的返回值设置为元组类型。例如
def return_two(i: Int) -> (Int, Int):
return i, i+1
a, b = return_two(5)
print(a, b)
# 5 6
同时,隐式的变量声明可以遮蔽全局不可变标识符(如模块名字或内建函数名)。例如
slice = foo()
自动解引用
Mojo
函数可以在结果类型说明符中使用一个新的 ref
关键字返回对存储的自动解引用。例如:
@value
struct Pair:
var first: Int
var second: Int
fn get_first_ref(inout self) -> ref[__lifetime_of(self)] Int:
return self.first
fn show_mutation():
var somePair = Pair(5, 6)
print(somePair.get_first_ref())
somePair.get_first_ref() = 1
print(somePair.get_first_ref())
# 5
# 1
这种方法提供了一种返回给定类型的 自动解引用 的通用方法。这一功能与 __refitem__
重叠,所以 __refitem__
已被删除,取而代之的是返回引用的 __getitem__
。
只推断参数
Mojo
增加了对只推断编译时参数的支持,即只能通过传入值来推断其类型,而不能手动指定。
只推断编译时参数必须出现在参数列表的开头,用户不能手动指定。它们被声明在 //
标记的左侧,就像位置参数一样。
定义这种带有依赖参数的函数,在调用时无需调用者指定所有必须参数。例如
fn parameter_simd[dt: DType, //, value: Scalar[dt]]():
print(value)
fn call_it():
parameter_simd[Int32(42)]()
也可以用在结构体中。例如
struct ScalarContainer[dt: DType, //, value: Scalar[dt]]:
pass
fn foo(x: ScalarContainer[Int32(0)]):
pass
编译时参数的重载
现在,可以对编译时参数进行重载了。例如
fn overloaded_parameters[value: Int32]():
pass
fn overloaded_parameters[value: Float32]():
pass
fn form_reference():
alias ref = overloaded_parameters[Int32()]
deprecated 装饰器
Mojo
现在支持在结构体、函数、特性、别名和全局变量上添加 @deprecated
装饰器。
该装饰器将所附声明标记为已废弃,在用户代码中引用已废弃声明时会发出警告。
@deprecated("Foo is deprecated, use Bar instead")
struct Foo:
pass
fn outdated_api(x: Foo): # warning: Foo is deprecated, use Bar instead
pass
@deprecated("use another function!")
fn bar():
pass
fn techdebt():
bar() # warning: use another function!
parameter for 参数
现在可以对 for
循环使用 @parameter
,用于替代原先的 unroll
装饰器,其中循环序列中使用的值必须是编译时参数值。例如
fn parameter_for[max: Int]():
@parameter
for i in range(max)
@parameter
if i == 10:
print("found 10!")
目前,@parameter for
要求序列的 __iter__
方法返回 _StridedRangeIterator
类型的值,将来可能会取消这一限制。
其他
Reference
和 AnyLifetime
的 is_mutable
编译时参数现在是 Bool
类型,而不是低级的 __mlir_type.i1
值。
现在,Mojo
将根据搜索路径(PATH
)上的 Python
链接到一个 Python
动态库。这样,您就可以激活一个虚拟环境(如 conda
),并访问安装在该环境中的 Python
模块,而无需设置 MOJO_PYTHON_LIBRARY
。
AnyRegType
已更名为 AnyTrivialRegType
,Mojo
现在禁止将非 trivial
型寄存器可传递类型绑定到 AnyTrivialRegType
。这弥补了语言中的一个主要安全漏洞。今后,通用代码请使用 AnyType
。
完全删除 let
关键字。
标准库层面
新的特性
内建的函数 repr
用于 Representable
特性
添加 Indexer
特性来表示实现了 __index__
方法的类型,可以在常见的 __getitem__
和 __setitem__
中使用,并可以对它们使用内置的 index
函数。
大多数标准库容器现在可以通过实现 Indexer
的任何类型进行索引。例如:
@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
struct MyList:
var data: List[Int]
fn __init__(inout self):
self.data = List[Int](1, 2, 3, 4)
fn __getitem__[T: Indexer](self, idx: T) -> Int:
return self.data[index(idx)]
print(MyList()[AlwaysZero()]) # 1
符合 Indexer
特性的类型可隐式转换为 Int
。这意味着您可以编写使用 Int
的通用 API
,而不必让它们使用符合 Indexer
的通用类型。例如
@value
struct AlwaysZero(Indexer):
fn __index__(self) -> Int:
return 0
@value
struct Incrementer:
fn __getitem__(self, idx: Int) -> Int:
return idx + 1
var a = Incrementer()
print(a[AlwaysZero()]) # 1
添加了几个支持内置函数和数学函数的特性。包括
函数 | 特性 | 魔法方法 |
---|---|---|
abs | Absable | __abs__ |
pow | Powable | __pow__ |
round | Roundable | __round__ |
math.ceil | math.Ceilable | __ceil__ |
math.ceildiv | math.CeilDivable/math.CeilDivableRaising | __ceildiv__ |
math.floor | math.Floorable | __floor__ |
math.trunc | Truncable | __trunc__ |
注意
- 实现
Powable
的类型可以使用**
操作符- 对于
ceildiv
,只要实现CeilDivable
或CeilDivableRaising
特性之一即可Ceilable
、CeilDivable
、Floorable
特性在math
包中,Truncable
目前是内置的,不需要导入,后面会再进行调整
使用方式如下
from math import sqrt
@value
struct Complex2(Absable, Roundable):
var re: Float64
var im: Float64
fn __abs__(self) -> Self:
return Self(sqrt(self.re * self.re + self.im * self.im), 0.0)
fn __round__(self) -> Self:
return Self(round(self.re, 0), round(self.im, 0))
fn __round__(self, ndigits: Int) -> Self:
return Self(round(self.re, ndigits), round(self.im, ndigits))
benchmark
在 benchmark 包中添加了 Bencher 模块用于基准测试
String
现已删除内置类/类型隐式转换为 String
类型。需要使用 str
将类型转换为字符串。
添加了符合 Python
通用分隔符的 String.isspace
方法。它取代了字符串模块中的 isspace
函数。
String.split
现在默认使用空白符,并具有 Pythonic
行为,即默认删除所有相邻的空白。
String.strip
、lstrip
和 rstrip
现在可以删除空白以外的自定义字符。
String
现在有了 splitlines
方法,可以在行边界分割字符串。该方法支持通用换行符,并提供了保留或删除换行符的选项。
InlinedString
已重命名为 InlineString
以与其他类型保持一致。
StringRef
现在实现了 strip
,可用于移除前导和尾部空白,并实现了 startswith
和 endswith
。
新增了 StringSlice
类型,以取代标准库代码中使用的不安全 StringRef
类型。StringSlice
是对编码字符串数据的非所有权引用。
与 StringRef
不同,StringSlice
与其指向的数据的生命周期安全地绑定在一起。
为 String
和 StringLiteral
添加了新的 as_string_slice
方法。
可以使用 UnsafePointer
和长度来初始化 StringSlice
。
为 String
和 StringLiteral
添加了新的 as_bytes_slice
方法,能够返回字符串所有字节的 Span
类型。
继续为 String
类型添加转换为 UnsafePointer
和无符号字节类型的支持:
- 将
String._as_ptr
更名为String.unsafe_ptr
,并将返回类型更改为UnsafePointer
- 将
StringLiteral.data
更名为StringLiteral.unsafe_ptr
,并将返回类型更改为UnsafePointer
InlineString.as_ptr
已更名为unsafe_ptr
,现在返回UnsafePointer[UInt8]
StringRef.data
现在是UnsafePointer
,StringRef.unsafe_ptr
现在返回UnsafePointer[UInt8]
其他内建函数
Slice.__len__
函数已被删除,Slice
不再符合 Sized
特性。这避免了语义上的歧义:切片的长度始终取决于被切片对象的长度。
如果需要使用现有功能可以使用 Slice.unsafe_indices
方法。该方法明确指出,该实现不会检查切片边界是否具体或在任何给定对象的长度范围内。
为符合 ComparableCollectionElement
特性的元素列表添加了内置 sort
函数。
int
现在可以使用字符串和指定的进制数将字符串解析为整数:int("ff", 16)
返回 255
。
此外,如果指定进制为 0
,那么字符串将被当作整数字面进行解析,进制由字符串是否包含前缀 "0x"
、"0o"
或 "0b"
决定。
添加了 bin
内置函数,用于将整数类型转换为二进制字符串表示。
添加了 atof
内置函数,它可以将字符串转换为 float64
。
现在,你可以使用内置的 any
和 all
函数来检查集合中的布尔元素。
由于 SIMD.__bool__
现在限制为 size=1
,因此必须显式地使用这些函数才能获得具有多个元素的 SIMD
向量的布尔值。这避免了 SIMD
与 Bool
之间隐式转换的常见错误。例如
fn truthy_simd():
var vec = SIMD[DType.int32, 4](0, 1, 2, 3)
if any(vec):
print("any elements are truthy")
if all(vec):
print("all elements are truthy")
# any elements are truthy
现在 object
现在实现了所有位运算符。
Tuple
实现了 __contains__
方法,可以进行成员判断。例如
var x = Tuple(1, 2, True)
if 1 in x:
print("x contains 1")
ListLiteral
和 Tuple
现在只要求元素类型是可移动的。因此,ListLiteral
和 Tuple
本身不再是可复制的。
UnsafePointer
为 UnsafePointer[Scalar[_]]
指针添加了新的 memcpy
重载。
删除了 UnsafePointer
和其他指针类型中的 get_null
方法。请使用默认构造函数:UnsafePointer[T]()
许多返回指针类型的函数已统一为 unsafe_ptr
公共 API
函数。
集合
List
现在有了一个 index
方法,可以让你找到一个元素在 EqualityComparable
类型的 List
中的(第一个)位置。例如
var my_list = List[Int](2, 3, 5, 7, 3)
print(my_list.index(3)) # prints 1
List
现在可以用简化的语法转换为 String
:
var my_list = List[Int](2, 3)
print(my_list.__str__()) # prints [2, 3]
请注意,List
还不符合 Stringable
特性,因此您还不能使用 str(my_list)
。
List
有了调用 count
方法的简化语法:my_list.count(x)
。
List
现在支持 __contains__
,因此现在可以使用列表和 in
操作符:
if x in my_list:
List
现在有了 unsafe_get
,可以在不进行边界检查或包络负索引的情况下获取元素的引用。请注意,此方法是不安全的。请谨慎使用。
为 Dict
添加了 fromkeys
方法,以返回包含指定键和值的 Dict
。添加了 clear
方法删除所有元素。
Dict
现在支持 items
和 values
迭代器的 reversed
方法。
Dict
现在可以通过 my_dict.__str__
简化为字符串。请注意,Dict
并不符合 Stringable
特性,因此 str(my_dict)
还无法实现。
Dict
现在实现了 get(key)
和 get(key, default)
函数。
为 Dict
添加了一个临时的 __get_ref(key)
方法,允许你获取字典值的引用。
添加了一个新的 InlineList
类型,这是一个具有静态最大尺寸的栈分配列表。
添加了一种新的 Span
类型,用于获取连续集合的片段。
os
os
模块现在提供了使用 mkdir
和 rmdir
添加和删除目录的功能。
添加了 os.path.getsize
函数,该函数以字节为单位给出路径所标识文件的大小。
添加了 os.path.join
函数
新增 tempfile
模块,以及 gettempdir
和 mkdtemp
函数。
SIMD
已添加带 StaticIntTuple
掩码的 SIMD.shuffle
。SIMD.__bool__
受到限制,只有当大小为 1
时才能工作。对于具有多个元素的 SIMD
向量,请使用 any
或 all
SIMD.reduce_or
和 SIMD.reduce_and
方法现在是位操作,并支持整数类型。
已添加 SIMD.__repr__
,以获取 SIMD
类型的详细字符串表示。
math
math.bit
模块已移至新的顶级 bit
模块。该模块中的下列函数已重命名:
- ctlz -> countl_zero
- cttz -> countr_zero
- bit_length -> bit_width
- ctpop -> pop_count
- bswap -> byte_swap
- bitreverse -> bit_reverse
函数 math.rot_bits_left
和 math.rot_bits_right
已经移动到 bit
模块。
math
模块中的 is_power_of_2
函数现在被称为 is_power_of_two
,位于 bit
模块中。
abs
、round
、min
、max
、pow
和 divmod
函数已经从 math
移到了内置函数中,因此您不再需要导入这些函数。
math.tgamma
函数已更名为 math.gamma
,以符合 Python
的命名方式。
以下函数的实现已从 math
模块移至新的 utils.numerics
模块:isfinite
、isinf
、isan
、nan
、nextafter
和 ulp
。
math.gcd
现在可以处理负输入,并与 Python
的实现一样,接受一个可变的整数列表。还新增了针对 List
或 Span
整数的重载。
协程
Coroutine
现在需要一个 lifetime
参数。解析器会在调用异步函数时自动设置该参数。它包含所有参数的生命周期以及参数的任何生命周期访问。这样就能确保只要 coroutine
仍在运行,异步函数捕获的参数就会保持有效。
Async
函数调用不再允许借用非 tirvial
的寄存器可传递类型。因为异步函数捕获它们的参数,但是寄存器可传递的类型没有生存期,所以 Mojo
不能正确地跟踪引用,这使得它不安全。为了弥补这个安全漏洞,Mojo
暂时禁止将非 trivial
的寄存器可传递类型绑定到异步函数中的借用参数。
其他
增加了一个 InlineArray
类型,它只适用于内存类型。与现有的 StaticTuple
类型相比,它在概念上是一个数组类型,但只适用于 AnyTrivialRegType
。
base64
包现在包括对 base64
和 Base16编
码方案的编码和解码支持。
Variant
和 Optional
中的 take
函数已重命名为u nsafe_take
。
Variant
中的 get
函数已被 __getitem__
所取代。也就是说,v.get[T]()
应该被 v[T]
代替。
algorithm
模块中的各种函数现在都是内置函数。这包括 sort
、swap
和 partition
。swap
和 partition
可能会在重新编写内置 sort
函数时对其进行优化。