Mojo 学习 —— 基本语法

Mojo 学习 —— 基本语法


Mojo 首先是为高性能系统编程而设计的,它具有强大的类型检查、内存安全、新一代编译器技术等特性。因此, MojoC++Rust 等语言有很多共同之处。

我觉得它很多地方更像 rust 一点,例如它没有继承,但是可以用特性(trait)来实现相同的功能。

文档中也提到了,Mojo 是一种年轻的语言,有许多特性还不完整。目前并不适合初学者,即使是这个基础部分的介绍也需要一些编程经验。^v^

语法

Mojo 是作为 Python 的超集而设计的,基本沿用了 Python 的语法,所以很多 Mojo 代码看起来和 Python 很像。

变量

变量是一个存放值或对象的名称。在 Mojo 中,所有变量都是可变的,如果要定义在运行时不可变的变量,可以使用定义别名(alias)的方式。

在早期的 Mojo 版本中,可以用 let 来声明不可变变量,但是后面为了简化语言,以及一些其他原因,把它删除了

未声明变量

Mojo 中允许使用 Python 形式的变量定义方式,即

name = value

但是这种声明方式只能在 def 函数和 REPL 环境中使用,不能在 fn 函数和 struct 范围内使用,所以尽量不要使用这种方式

声明变量

Mojo 使用 var 关键字来声明变量,可以在声明的时候直接赋值

var name = 'Tom'

编译器会自动推断出变量 name 的类型,此时 name 的类型为 StringIteral,即字符串字面量

也可以先声明变量的类型,然后延迟赋值,即后面用到之后在给它赋值。例如

var age: Int
age = 19

这个可以理解为 Python 中的,先给变量赋值一个 None,后面修改变量的值

age = None
age = 19

Mojo 声明是强类型的,不能将一种类型的值赋值给另一种类型的值,除非目标类型定义了隐式转换规则。例如,将一个字符串类型(StringIteral)的值赋值给一个整数类型的变量,会报错

var age: Int = '19'
// error: cannot implicitly convert 'StringLiteral' value to 'Int' in 'var' initializer

反过来的话,将一个整数赋值给字符串,是可以的

var name: String = 19

这是为什么呢?

其实,这种赋值方式调用了 String 类型的构造函数(与 Python 中的 __init__ 类似),即下面两种方式是等价的

var name: String = 19
var name = String(19)

也就是说,构造函数可以接受一个整数类型的值,并将其转换为 String 类型,这个构造函数的形式大概是这样子的

fn __init__(inout self, num: Int)
两种方式的区别

这两种方式在变量作用域上会有一些区别。例如,在 def 函数内

def lexical_scopes():
    var num = 10
    var dig = 1
    if True:
        print("num:", num)  # Reads the outer-scope "num"
        var num = 20        # Creates new inner-scope "num"
        print("num:", num)  # Reads the inner-scope "num"
        dig = 2             # Edits the outer-scope "dig"
    print("num:", num)      # Reads the outer-scope "num"
    print("dig:", dig)      # Reads the outer-scope "dig"

lexical_scopes()
# num: 10
# num: 20
# num: 10
# dig: 2

varif 语句块内定义一个新的变量,遮蔽了外部变量的值,同时它可以修改在外部定义的变量的值。而对于未声明的方式

def function_scopes():
    num = 1
    if num == 1:
        print(num)   # Reads the function-scope "num"
        num = 2      # Updates the function-scope variable
        print(num)   # Reads the function-scope "num"
    print(num)       # Reads the function-scope "num"

function_scopes()
# 1
# 2
# 2

可以看成是赋值,会修改变量的值,而不是遮蔽外部变量

函数

Mojo 函数可以使用 fndef 来声明。

  • fnRust style):强制类型检查和内存安全行为
fn greet2(name: String) -> String:
    return "Hello, " + name + "!"
  • defPython style):可以不声明类型且包含动态行为
def greet(name):
    return "Hello, " + name + "!"

这两个函数返回结果是一样的,但是 fn 函数提供了编译时检查,以确保函数接收和返回正确的类型。而 def 函数如果接收到错误的类型,可能会在运行时失败。

目前 Mojo 不支持顶层代码,需要在 .mojo 中定义一个 mian 函数,作为程序的入口。可以使用 deffn 定义

fn main():
    print("Hello Mojo")
参数的值所有权问题

deffn 在参数值传递上也存在区别,def 函数是“按值”接收参数。而 fn 函数可以指定值的传递方式,包括三种

  1. owned:传递值
  2. inout:传递可变引用
  3. borrowed:传递不可变引用(借用)

这个特性与 Mojo 的值所有权模式有关,这一模式确保在任何给定时间只有一个变量“拥有”一个值(但允许其他变量接收对它的引用)来保护您免受内存错误的影响。然后,所有权确保在所有者的生命周期结束时销毁值(并且没有未完成的引用)。这一点与 rust 基本上是一样

在后面的章节中再详细讨论这一问题

结构体

数据类型可以用 struct 定义为结构体,它与 Python 中的 class 类似:都支持方法、字段、运算符重载以及用于元编程的装饰器等。

Mojo 的结构体是完全静态的,在编译时就已经绑定,不允许动态派发或在运行时对结构体进行任何更改。(Mojo 未来还将支持 Python 风格的类,但现在还不行)。

例如,定义一个包含两个变量的结构体

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)

使用也和 Python 中的类很像

fn main():
    var mine = MyPair(2, 4)
    mine.dump()

特性

特性这一点和 rust 基本一样,trait 我觉得它在某方面有点像是抽象类获取其他语言所说的接口,特性中定义的所有函数,结构体都要实现,才能说它符合该特性的。

目前特性只支持方法,且无法在 trait 中实现默认的行为。

使用特性,可以限制某些函数或结构体只能应用于实现了某种(或几种)特性的任何类型中,从而实现泛型

例如,定义一个必须包含 say 方法的特性

trait SomeTrait:
    fn say(self, x: Int): ...

然后创建一个符合该特性的结构体,需要实现 say 方法。

@value
struct SomeStruct(SomeTrait):
    fn say(self, x: Int):
        print("hello traits", x)

暂时别管 @value 在干嘛,它只是给结构体加了点东西

然后将 trait 作为函数的编译时参数类型,并调用该类型包含的 say 方法。

fn fun_with_traits[T: SomeTrait](x: T):
    x.say(42)

fn main():
    var thing = SomeStruct()
    fun_with_traits(thing)
// hello traits 42

关于中括号和小括号的区别,将在下面介绍

函数 fun_with_traits 定义了运行时参数 x 的类型是所有符合 SomeTrait 特性的数据类型,而所有符合 SomeTrait 特性的类型,都有一个 say 方法,所以这个变量一定可以调用 say 方法。

因此,fun_with_traits 被称为 “泛型函数”,因为它接受的是一种泛化的类型,而不是一种特定的类型。

参数化

在其他编程语言中,对于 parameterargument 并没有明显的区分,很多时候都把它们当做一个东西。

但是在 Mojo 中,这两个单词指的是不同的东西

  • parameter 指的是编译时参数,是一个编译时变量,在运行时会变成常数,并在方括号([])中声明
  • argument 指的是函数参数,是一个运行时参数,在圆括号(())中声明

看一个例子,定义一个 repeat 函数,可以打印字符串多次

fn repeat[count: Int](msg: String):
    for i in range(count):
        print(msg)

其中打印次数 countparameter,打印的字符串 msgargument

fn main():
    repeat[3]("Hello")

将会打印 Hello 三次

通过指定 count 作为编译时参数,Mojo 编译器可以优化函数,因为该值保证在运行时不会改变。编译器有效地生成了一个唯一版本的 repeat() 函数,该函数只重复信息 3 次。这使得代码的性能更高,因为运行时需要计算的内容更少。

类似地,也可以用在结构体上,更具体的内容后面再说。

与 Python 交互

目前 Mojo 还不是一个完整的 Python 超集,但可以导入 Python 中的模块

from python import Python

fn main():
    try:
        var re = Python.import_module('re')
        var list = re.findall(r'\d+', 'hello 42 I\'m a 32 string 30')
        print(list)
    except e:
        print(e)
# ['42', '32', '30']

其他

其他基本上和 Python 的语法差不多,像字符串可以使用单引号、双引号和三引号,使用缩进区分代码块,注释也是一样的,没啥好说

  • 36
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值