Julia Language

Gypsophila

作为一种专为科学计算设计的工具,Julia 的目标是构建一个前所未有的集易用、强大、高效于一体的语言,除此之外,Julia 还希望具有以下优势:

  • 采用 MIT 许可证:免费又开源
  • 用户自定义类型的速度与兼容性和内建类型一样好
  • 无需特意编写向量化的代码:非向量化的代码就很快
  • 为并行计算和分布式计算设计
  • 轻量级的“绿色”线程:协程
  • 低调又牛逼的类型系统
  • 优雅、可扩展的类型转换和类型提升
  • 对 Unicode 的有效支持,包括但不限于 UTF-8
  • 直接调用 C 函数,无需封装或调用特别的 API
  • 像 Shell 一样强大的管理其他进程的能力
  • 像 Lisp 一样的宏和其他元编程工具

这篇文章主要介绍如何使用 Julia 语言创建项目、管理依赖包等内容,以及一些特殊的的语法和特性。Julia 版本为 v1.11,使用的 IDE 为 VS Code。

项目的创建

Julia 项目的环境配置一般由项目文件Project.toml和清单文件Manifest.toml体现。在正式开发 Julia 项目的时候往往还需要专属的环境配置文件。这样才能与全局环境区别开。这么做有 3 个好处:

  1. 一旦有了专属的环境配置文件,我们的项目就可以独立地管理依赖包了。
  2. 针对某个 Julia 项目的程序包管理操作不会影响到全局的环境配置。反之亦然。
  3. 拥有环境配置文件的 Julia 项目可以为项目的分发(以供他人使用)做好准备。

如此一来,上述构建的 Julia 项目可以成为独立的、可重用的以及对分发友好的项目。

现在开始演示如何创建一个新的 Julia 项目。一般地,创建一个 Julia 项目分为以下几步:
Step 1. 首先在命令行中通过输入julia命令进入到 REPL 环境,接着键入shell进入 shell 模式中,并使用cd命令进入某个专用的目录(比如~/Projects)。此时可以使用pwd函数确认一下当前的目录:

1
2
julia> pwd()
"D:\\Projects"

Julia中的pwd函数的含义就是打印当前的工作目录,与在命令行中输入pwd命令的作用是类似的,只不过调用表达式pwd()的求值结果是一个字符串。

Step 2. 在确认了工作目录之后,按]键切换到 REPL 环境的 pkg 模式,然后输入generate命令,并后跟一个空格和项目的名称NewProj

1
2
3
4
(@v1.11) pkg> generate NewProj
Generating project NewProj:
NewProj\Project.toml
NewProj\src\NewProj.jl

注意,generate命令后面追加的参数是我们要创建的 Julia 项目的名称,这个命令在当前路径下创建了一个名为NewPorj的目录,并在该目录下生成了两个文件。一个是项目文件Project.toml,另一个是src目录(即源码目录)下的源码文件NewPorj.jl

我们先来看项目文件,它的内容如下:

1
2
3
4
name = "NewProj"
uuid = "8cf3351a-842d-4fb4-9c30-cffadf565175"
authors = ["Gypsophila-cx <1404384078@qq.com>"]
version = "0.1.0"

这里有 4 个条目,分别代表项目的名称、UUID、作者信息和初始版本号。其中的 UUID 是 Julia 的程序包管理器自动生成的。而项目作者信息是从当前操作系统中的 Git 配置信息复制过来的。

我们再来看源码文件NewProj.jl的内容:

1
2
3
4
5
module NewProj

greet() = print("Hello World!")

end # module NewProj

其中只定义了一个名为NewProj的模块。并且,该模块仅包含了一个可以向计算机的标准输出打印Hello World!的函数greet。这显然只是一个简单的程序模板。不过,它为我们后续的编码开了个头。

注意,这个源码文件是有重要意义的:

  1. 该文件可以被称为NewProj项目的源码入口。或者说,它是这个项目的主源码文件。这是由于该文件的主文件名与项目的(主)名称是一致的。
  2. 该文件中定义的(最外层的)模块NewProj将会是其所属项目的主模块(或者说默认模块)。这是由于该模块的名称与项目的(主)名称是一致的。

正因为有了这样的一个源码文件,使得NewProj项目可以被 Julia 视为一个程序包。更明确地讲,如果存在一个名为XX.jl的 Julia 项目,只要该项目包含一个相对路径为src/X.jl的源码文件,并且在该文件中定义的最外层模块名为X,那么它就是一个有效的程序包。

引入程序包

既然NewProj.jl项目已经是一个有效的程序包了,那么我们就可以在代码中对它进行引入(更明确地说,是引入它的主模块NewProj)。具体怎么做呢?

当我们试图在全局环境中导入该程序包的时候,Julia 会提示找不到这个程序包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
julia> import NewProj
ERROR: ArgumentError: Package NewProj not found in current path.
- Run `import Pkg; Pkg.add("NewProj")` to install the NewProj package.
Stacktrace:
[1] macro expansion
@ .\loading.jl:2223 [inlined]
[2] macro expansion
@ .\lock.jl:273 [inlined]
[3] __require(into::Module, mod::Symbol)
@ Base .\loading.jl:2198
[4] #invoke_in_world#3
@ .\essentials.jl:1089 [inlined]
[5] invoke_in_world
@ .\essentials.jl:1086 [inlined]
[6] require(into::Module, mod::Symbol)
@ Base .\loading.jl:2191

为了解决这个问题,需要首先在 REPL 环境下使用;命令进入 shell 模式,接着使用cd NewProj命令进入到NewProj.jl项目所在的目录,然后切换到 pkg 模式,并输入命令activate .。注意,这里的输入是activate加一个空格 ,再加一个英文点号.。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
julia> pwd()
"D:\\Projects"
# tap `;` to enter shell mode
shell> cd NewProj
D:\Projects\NewProj
# tap `backspace` to exit shell mode, then tap `]` to enter pkg mode
(@v1.11) pkg> activate .
Activating project at `D:\Projects\NewProj`
# Now the env is changed to 'NewProj'
(NewProj) pkg> activate
Activating project at `C:\Users\chenx\.julia\environments\v1.11`
# change back to the global env
(@v1.11) pkg>

命令activate .的作用是把程序包管理器的操作目录切换到当前项目所在的目录,即:./NewProj,而原本默认的操作目录是\.julia\environments\v1.11,对应于 Julia 的v1.11版本的全局环境。如果希望切换回全局环境,那么只需要再次输入命令activate(不加任何参数)即可。

在这之后,我们再在当前的 REPL 环境中导入NewProj就不会有问题了:

1
2
3
4
5
julia> import NewProj
[ Info: Precompiling NewProj [8cf3351a-842d-4fb4-9c30-cffadf565175]

julia> NewProj.greet()
Hello World!

如果确实需要在全局环境中引入NewProj,那么可以先把这个项目上传到一个代码托管仓库(比如 GitHub)中,然后再使用 Julia 的程序包管理器把它安装到本地的仓库目录。

比如,如果NewProj.jl项目以及被托管到了 GitHub 上了,经查询可知它的 git 地址是git@github.com:gypsophila-cx/NewProj.git,那么现在就可以直接在 REPL 环境中进行如下操作将它纳入到全局环境中:

1
(v1.11) pkg> add git@github.com:gypsophila-cx/NewProj.git

一旦NewProj程序包被记录在了全局环境的项目文件中,在全局环境下引入它也就不会有问题了。

代码编写

函数

通常情况下,我们在 Julia 中定义函数的方式是这样的:

1
2
3
4
function IterativeSovler(A::AbstractMatrix{T}, b::Vector{T}, tol::T=1e-6, max_iter::Int=1000) where T
# some code
return x
end

上述代码给出了一个编写迭代求解器的示例。在 Julia 中,函数的参数类型是可以显式声明的,也可以不声明,这取决于程序的需求。类型声明能够帮助编译器做出优化,并为用户提供更清晰的接口。虽然类型声明不是强制性的,但在某些场合,显式声明参数类型能够提高性能、避免错误,并提升代码的可读性,而在编写时不指定类型会增加运行时开销,因为 Julia 需要动态决定如何处理参数类型。

另外,上述代码中where T语句用于定义类型参数(type parameters),它表示函数、类型或结构体中使用的类型变量,这里where T使得函数IterativeSovler的类型能够适配不同的数据类型。这样做的好处有:

  1. 泛化:where T使得IterativeSovler函数可以处理不同类型的数据。例如,如果传入一个Float64矩阵A和向量b,那么T就会被推断为Float64;如果传入Complex{Float64}类型的矩阵和向量,T就会被推断为Complex{Float64}。这使得函数变得更加灵活,能够处理各种数据类型。
  2. 类型安全:使用类型参数T允许在编译时进行类型检查,从而避免类型错误。编译器会确保在调用函数时传入的所有参数(矩阵A和向量b)都具有一致的类型。

相关资料

  • Title: Julia Language
  • Author: Gypsophila
  • Created at : 2025-01-20 13:59:13
  • Updated at : 2025-01-25 23:58:43
  • Link: https://chenx.space/2025/01/20/Julia/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments