Mojo 会火的 AI 编程语言

本文介绍了Mojo,一种结合了Python的易用性和系统编程性能的编程语言。Mojo支持Python语法,但提供了强类型检查和内存安全,以及编译语言的性能。文章概述了主要特点,如`fn`函数的使用、变量声明、所有权和Python模块的集成。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Copyright 2023 Modular, Inc: Licensed under the Apache License v2.0 with LLVM Exceptions.

Mojo 语言基础

Mojo 是一种功能强大的编程语言,主要用于高性能系统编程,因此与 Rust 和 C++ 等其他系统语言有很多共同之处。然而,Mojo 也被设计成 Python 的超集,因此您可能从 Python 中了解到的许多语言特性和概念都可以很好地转换到 Mojo 中。

例如,如果您在 REPL 环境或 Jupyter 笔记本(如本文档)中,您可以像 Python 一样运行顶层代码:

In [1]:

print("Hello Mojo!")
Hello Mojo!

这在其他系统编程语言中通常是看不到的。

Mojo 保留了 Python 的动态特性和语言语法,甚至允许从 Python 包中导入和运行代码。不过,重要的是要知道,Mojo 是一种全新的语言,而不仅仅是一种带有语法糖的 Python 新实现。Mojo 将 Python 语言提升到了一个全新的水平,包括系统编程功能、强大的类型检查、内存安全、新一代编译器技术等。然而,它仍然被设计成一种简单的语言,适用于通用编程。

本页将温和地介绍 Mojo 语言,只需要一点点编程经验。让我们开始吧!

如果您是一位经验丰富的系统程序员,并希望深入了解该语言,请查阅 Mojo 编程手册

语言基础

首先,Mojo 是一种编译语言,它的许多性能和内存安全特性都源于此。Mojo 代码可以进行超前编译(AOT)或实时编译(JIT)。

与其他编译语言一样,Mojo 程序(.mojo 或 .🔥文件)需要一个 main() 函数作为程序的入口。例如:

In [2]:

fn main():
    var x: Int = 1
    x += 1
    print(x)

如果你了解 Python,你可能会想到函数名应该是 def main() 而不是 fn main()。实际上,这两种方法在 Mojo 中都能使用,但使用 fn 时的行为有些不同,我们将在下文讨论。

当然,如果您构建的是一个 Mojo 模块(一个 API 库),而不是一个 Mojo 程序,那么您的文件就不需要 main() 函数(因为它将被其他有 main() 函数的程序导入)。

注意: 注意:在 .mojo/.🔥 文件中编写代码时,不能运行本页所示的顶层代码--Mojo 程序或模块中的所有代码都必须封装在函数或结构体中。不过,顶层代码可以在 REPL 或 Jupyter Notebook(如当前的 Jupyter Notebook)中运行。

现在让我们来解释一下这个 main() 函数中的代码。

语法和语义

这很简单: Mojo 支持(或将支持)所有 Python 语法和语义。如果您不熟悉 Python 的语法,网上有大量的资源可以教您。

例如,与 Python 一样,Mojo 使用换行和缩进来定义代码块(而不是大括号),并且 Mojo 支持所有 Python 的控制流语法,如 if 条件和 for 循环。

不过,Mojo 仍在不断完善中,所以有些来自 Python 的功能还没有在 Mojo 中实现(参见 Mojo 路线图)。所有缺失的 Python 功能都会及时出现,但 Mojo 已经包含了许多 Python 以外的功能和特性。

因此,以下章节将重点介绍 Mojo 独有的一些语言特性(与 Python 相比)。

函数

Mojo 函数可以用 fn(如上图所示)或 def(如 Python)来声明。fn 声明强制执行强类型和内存安全行为,而 def 则提供 Python 风格的动态行为。

fn 和 def 函数都有其价值,学习这两种函数非常重要。不过,在本介绍中,我们将只关注 fn 函数。有关这两种函数的更多详情,请参阅编程手册

在以下章节中,您将了解 fn 函数如何在代码中执行强类型和内存安全行为。

变量

您可以使用 var 声明变量(如上文 main() 函数中的 x),以创建可变值,或使用 let 声明变量,以创建不可变值。

error: Expression [15]:7:5: expression must be mutable for in-place operator destination
    x += 1
    ^

这是因为 let 使值不可变,所以不能递增。

如果完全删除 var,就会出错,因为 fn 函数需要明确的变量声明(与 Python 风格的 def 函数不同)。

后,请注意 x 变量有明确的 Int 类型说明。fn 中的变量并不需要声明类型,但有时需要。如果省略,Mojo 会推断类型,如图所示:

In [3]:

fn do_math():
    let x: Int = 1
    let y = 2
    print(x + y)

do_math()
3

函数参数和返回值

虽然函数体中声明的变量不需要类型,但 fn 函数的参数和返回值需要类型。

例如,下面是如何将 Int 声明为函数参数和返回值的类型:

In [4]:

fn add(x: Int, y: Int) -> Int:
    return x + y

z = add(1, 2)
print(z)
3
可选参数和关键字参数

您还可以指定参数默认值(也称为可选参数),并使用关键字参数名称传递值。例如:

In [5]:

fn pow(base: Int, exp: Int = 2) -> Int:
    return base ** exp

# Uses default value for `exp`
z = pow(3)
print(z)

# Uses keyword argument names (with order reversed)
z = pow(exp=3, base=2)
print(z)
9
8

注意: Mojo 目前只支持部分关键字参数,因此还不支持某些功能,如只支持关键字参数和可变关键字参数(如 **kwargs)。

参数可变性和所有权

Mojo 支持完整的值语义,并通过强大的值所有权模型(类似于 Rust 借用检查器)实现内存安全。因此,下面将快速介绍如何通过函数参数共享对值的引用。

请注意,上面的 add() 函数并不修改 x 或 y,它只是读取值。事实上,按照写法,函数不能修改它们,因为 fn 参数默认是不可变的引用

在参数约定方面,这被称为 borrowed,虽然这是 fn 函数的默认设置,但你可以通过这样的借用声明将其明确化(其行为与上面的 add() 完全相同):

In [6]:

fn add(borrowed x: Int, borrowed y: Int) -> Int:
    return x + y

如果希望参数是可变的,则需要将参数约定声明为 inout。这意味着在函数内部对参数所做的更改在函数外部是可见的。

例如,该函数可以修改原始变量:

In [7]:

fn add_inout(inout x: Int, inout y: Int) -> Int:
    x += 1
    y += 1
    return x + y

var a = 1
var b = 2
c = add_inout(a, b)
print(a)
print(b)
print(c)
2
3
5

另一种方法是将参数声明为 owned 参数,这样函数就拥有了参数值的完全所有权(参数值可变且保证唯一)。这样,函数就可以修改值,而不必担心影响函数外部的变量。例如:

In [8]:

fn set_fire(owned text: String) -> String:
    text += "🔥"
    return text

fn mojo():
    let a: String = "mojo"
    let b = set_fire(a)
    print(a)
    print(b)

mojo()
mojo
mojo🔥

在本例中,Mojo 复制了 a 并将其作为 text 参数传递。原始的 a 字符串仍然完好无损。

但是,如果你想让函数拥有值的所有权,而不想进行复制(这对某些类型来说可能是一个昂贵的操作),那么你可以在将 a 传递给函数时添加 ^ "转移 "操作符。转移操作符会有效地销毁局部变量名--以后任何调用它的尝试都会导致编译器错误。

试试上面的方法,将 set_fire() 的调用改成下面的样子:

mojo
    let b = set_fire(a^)

现在你会得到一个错误,因为转移操作符实际上破坏了 a 变量,所以当下面的 print() 函数试图使用 a 时,该变量就不再初始化了。

如果删除 print(a),则可以正常工作。

这些参数约定旨在为系统程序员提供全面的内存优化控制,同时确保安全访问和及时删除--Mojo 编译器确保没有两个变量可以同时访问相同的值,每个值的生命周期都定义得很好,以严格防止任何内存错误,如 "use-after-free "和 "double-free"。

注意: 目前,Mojo 总是在函数返回值时复制一个副本。

结构

您可以在 struct 中为类型(或 "对象")建立高级抽象。Mojo 中的 struct 类似于 Python 中的 class:它们都支持方法、字段、运算符重载、用于元编程的装饰器等。不过,Mojo 的结构体是完全静态的--它们在编译时就已绑定,因此不允许动态派发或在运行时对结构体进行任何更改。(Mojo 未来还将支持类)。

例如,下面是一个基本的结构体:

In [9]:

struct MyPair:
    var first: Int
    var second: Int

    fn __init__(inout self, first: Int, second: Int):
        self.first = first
        self.second = second

    fn dump(self):
        print(self.first, self.second)

下面是如何使用它的方法:

In [10]:

let mine = MyPair(2, 4)
mine.dump()
2 4

如果您熟悉 Python,那么 __init__() 方法和 self 参数应该不会陌生。如果您不熟悉 Python,那么请注意,当我们调用 dump() 时,实际上并没有为 self 参数传递值。self 的值是结构体的当前实例自动提供的(它的用法类似于其他语言中用来指代对象/类型的当前实例的 this name)。

有关结构体和其他特殊方法(如 __init__() 方法,也称为 "dunder "方法)的更多详情,请参阅编程手册

Python 集成

尽管 Mojo 仍在不断完善之中,还不是 Python 的完全超集,但我们已经建立了一个原样导入 Python 模块的机制,因此您可以立即利用现有的 Python 代码。在引擎盖下,该机制使用 CPython 解释器运行 Python 代码,因此能与目前所有的 Python 模块无缝兼容。

例如,下面是导入和使用 NumPy 的方法(必须安装 Python numpy):

In [11]:

from python import Python

let np = Python.import_module("numpy")

ar = np.arange(15).reshape(3, 5)
print(ar)
print(ar.shape)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
(3, 5)

注意: Mojo 还不是 Python 功能完备的超集。因此,您不能总是复制 Python 代码并在 Mojo 中运行。有关我们计划的更多细节请参阅 Mojo🔥 roadmap & sharp edges

注意: 安装 Mojo 时,安装程序会搜索系统中与 Mojo 配合使用的 Python 版本,并将路径添加到 modular.cfg 配置文件中。如果您更改了 Python 版本或切换了虚拟环境,Mojo 将使用错误的 Python 库,这可能会导致一些问题,例如导入 Python 包时出错(Mojo 只显示在 Python 中发生了错误,这是一个单独的已知问题)。目前的解决方案是使用 MOJO_PYTHON_LIBRARY 环境变量来覆盖 Mojo 的 Python 库路径。有关如何查找和设置此路径的说明,请参阅此相关问题

最后

我们希望本页介绍的基础知识足以让您入门。由于篇幅有限,如果您想了解更多相关内容,请查阅 Mojo 编程手册

注意: Mojo SDK 仍处于早期开发阶段。有些东西还很粗糙,但您可以期待语言和工具的不断变化和改进。请查看已知问题,并在 GitHub 上报告任何其他问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值