julia 是什么?
Julia 是一种为科学计算而生的,开源、多平台、高性能的高级编程语言。
Julia 有一个基于 LLVM 的 JIT 编译器,这让使用者无需编写底层的代码也能拥有像 C 与 FORTRAN 那样的性能。因为代码在运行中编译,你可以在 shell 或 REPL 中运行代码,这也是一种推荐的工作流程。
Julia 是动态类型的。并且提供了为并行计算和分布式运算设计的多重派发机制。
Julia 自带包管理器。
Julia 有许多内置的数学函数,包括特殊函数 (例如:Gamma 函数)。并且支持开箱即用的复数运算。
Julia 允许你通过类似 Lisp 的宏来自动生成代码。
Julia 诞生于 2012 年。
基础语法
| 赋值语句 | answer = 42x, y, z = 1, [1:10; ], A stringx, y = y, x # 交换 x, y |
| 常量定义 | const DATE_OF_BIRTH = 2012 |
| 行尾注释 | i = 1 # 这是一行注释 |
| 多行注释 | #= 这是另一行注释 =# |
| 链式操作 | x = y = z = 1 # 从右向左0 < x < 3 # true5 < x != y < 5 # false |
| 函数定义 | function add_one(i)return i + 1end |
| 插入 LaTeX 符号 | \\delta + [Tab] # δ |
运算符
| 基本算数运算 | +,-,*,/ |
| 幂运算 | 2^3 # 8 |
| 除法 | 3/12 # 0.25 |
| 反向除法 | 7\\3 == 3/7 # true |
| 取余 | x % y 或 rem(x,y) |
| 取反 | !true # false |
| 等于 | a == b |
| 不等于 | a != b 或 a ≠ b |
| 小于与大于 | < 与 > |
| 小于等于 | <= 或 ≤ |
| 大于等于 | >= 或 ≥ |
| 逐元素运算(点运算) | [1, 2, 3] .+ [1, 2, 3] == [2, 4, 6] # true[1, 2, 3] .* [1, 2, 3] == [1, 4, 9] # true |
| 检测非数值(NaN) | isnan(NaN) # true 而不是 NaN == NaN # false |
| 三元运算符 | a == b ? Equal : Not equal |
| 短路 AND 和 OR 表达式 | a && b 和 a || b |
| 对象等价 | a === b |
shell/REPL 环境
| 上一次运算的结果 | ans |
| 中断命令执行 | [Ctrl] + [C] |
| 清屏 | [Ctrl] + [L] |
| 运行程序文件 | include(filename.jl) |
查找 func 相关的帮助 |
?func |
查找 func 的所有定义 |
apropos(func) |
| 命令行模式 | ; |
| 包管理模式 | ] |
| 帮助模式 | ? |
| 查找特殊符号输入方式 | ?☆ # ☆ can be typed by \\bigwhitestar<tab> |
| 退出特殊模式 返回到 REPL |
在空行上按 [Backspace] |
| 退出 REPL | exit() 或 [Ctrl] + [D] |
标准库
为了让 Julia 能加载的更快,许多核心组件都放在与 Julia 捆绑在一起的标准库中。 当你想用某一个标准库时,就输入 using PackageName。 以下是一些标准库及其常用的函数。
Random |
rand, randn, randsubseq |
Statistics |
mean, std, cor, median, quantile |
LinearAlgebra |
I, eigvals, eigvecs, det, cholesky |
SparseArrays |
sparse, SparseVector, SparseMatrixCSC |
Distributed |
@distributed, pmap, addprocs |
Dates |
DateTime, Date |
包管理
一个程序包必须先注册,然后才能在包管理器中看到它。
在 Julia 1.0 中,有两种使用包管理器的方法:
- 一是通过
using Pkg导入Pkg模块,然后用它的函数管理其他包; - 或者在 REPL 中输入
],然后按回车。进入特殊的交互式包管理模式。 (要从包管理模式返回 REPL,只需要在空行上按退格键BACKSPACE就行了)
注意新的工具总是先添加到交互式模式中,然后才会加入 Pkg 模块。
在 Julia 会话中使用 Pkg 管理包
| 列出已安装的包 (人类可读版) | Pkg.status() |
| 更新所有包 | Pkg.update() |
| 安装包 | Pkg.add(PackageName) |
| 重新构建包 | Pkg.build(PackageName) |
| (在安装之后) 使用包 | using PackageName |
| 删除包 | Pkg.rm(PackageName) |
交互式包管理模式
| 添加包 | add PackageName |
| 删除包 | rm PackageName |
| 更新包 | update PackageName |
| 使用开发版本 | dev PackageName 或 dev GitRepoUrl |
| 停止使用开发板,返回普通的发行版 | free PackageName |
字符与字符串
| 字符 | chr = C |
| 字符串 | str = A string |
| 字符 => 编码 | Int(J) # 74 |
| 编码 => 字符 | Char(74) # J |
| 任意的 UTF 字符 | chr = \\uXXXX # 4 位 HEXchr = \\UXXXXXXXX # 8 位 HEX |
| 逐字符迭代 | for c in strprintln(c)end |
| 字符串拼接 | str = Learn * * Julia |
| 字符插值 | a = b = 2println(a * b = $(a*b)) |
| 第一个匹配的子串或正则表达式 | findfirst(isequal(i), Julia) # 4 |
| 替换字串或正则表达式 | replace(Julia, a => us)# Julius |
| 收集的最后一个索引值 | lastindex(Hello) # 5 |
| 字符串的长度 | length(Hello) # 5 |
| 正则表达式 | pattern = rl[aeiou] |
| 子字符串 | str = +1 234 567 890pat = r\\+([0-9]) ([0-9]+)m = match(pat, str)m.captures # [1, 234] |
| 所有匹配 | [m.match for m = eachmatch(pat, str)] |
| 所有匹配的迭代器 | eachmatch(pat, str) |
要当心 UTF-8 中的多字节 Unicode 编码:
Unicode_string = Ångström
lastindex(Unicode_string) # 10
length(Unicode_string) # 8
Unicode_string[10] # m: ASCII/Unicode U+006d
Unicode_string[9] # ERROR: StringIndexError(Ångström, 9)
Unicode_string[8] # ö: Unicode U+00f6
字符串是不可变的。
数字相关
| 整数类型 | IntN 和 UIntN, 且 N ∈ {8, 16, 32, 64, 128}, BigInt |
| 浮点类型 | FloatN 且 N ∈ {16, 32, 64}BigFloat |
| 类型的最大和最小值 | typemin(Int8)typemax(Int64) |
| 复数类型 | Complex{T<:Real} |
| 虚数单位 | im |
| 机器精度 | eps() # 等价于 eps(Float64) |
| 圆整 | round() # 浮点数圆整round(Int, x) # 整数圆整 |
| 类型转换 | convert(TypeName, val) # 尝试进行转换/可能会报错TypeName(val) # 调用类型构造器转换 |
| 全局常量 | pi # 3.1415...π # 3.1415...im # real(im * im) == -1 |
| 更多常量 | using Base.MathConstants |
Julia 不会自动检测数值溢出。 使用 SaferIntegers 包可以得到带溢出检查的整数。
随机数
许多随机数函数都需要 using Random。
| 设置随机数种子 | Random.seed!(seed) |
| 产生随机数 | rand() # 均匀分布 [0,1)randn() # 正态分布 (-Inf, Inf) |
| 产生特定分布的随机数 | using Distributionsmy_dist = Bernoulli(0.2) # 举例rand(my_dist) |
| 以概率 p 从 A 中进行伯努利抽样 | randsubseq(A, p) |
| 随机重排 A 中的元素 | shuffle(A) |
数组
| 声明数组 | arr = Float64[] |
| 预分配内存 | sizehint!(arr, 10^4) |
| 访问与赋值 | arr = Any[1,2]arr[1] = Some text |
| 数组比较 | a = [1:10;]b = a # b 指向 aa[1] = -99a == b # true |
| 复制元素(而不是地址)/深拷贝 | b = copy(a)b = deepcopy(a) |
| 从 m 到 n 的子数组 | arr[m:n] |
| n 个 0.0 填充的数组 | zeros(n) |
| n 个 1.0 填充的数组 | ones(n) |
| n 个 #undef 填充的数组 | Vector{Type}(undef,n) |
| n 个从 start 到 stop 的等间距数 | range(start,stop=stop,length=n) |
| n 个随机 Int8 填充的数组 | rand(Int8, n) |
| 用值 val 填充数组 | fill!(arr, val) |
| 弹出最后一个元素 | pop!(arr) |
| 弹出第一个元素 | popfirst!(a) |
| 将值 val 作为最后一个元素压入数组 | push!(arr, val) |
| 将值 val 作为第一个元素压入数组 | pushfirst!(arr, val) |
| 删除指定索引值的元素 | deleteat!(arr, idx) |
| 数组排序 | sort!(arr) |
| 将 b 连接到 a 后 | append!(a,b) |
| 检查值 val 是否在数组 arr 中 | in(val, arr) 或 val in arr |
| 改变维数 | reshape(1:6, 3, 2) == [1 2 3; 4 5 6] |
| 转化为字符串,并以 delim 分隔 | join(arr, delim) |
线性代数
想要使用线性代数相关的工具,请用:using LinearAlgebra。
| 单位矩阵 | I # 直接用 I 就好。会自动转换到所需的维数。 |
| 定义矩阵 | M = [1 0; 0 1] |
| 矩阵维数 | size(M) |
选出第 i 行 |
M[i, :] |
选出第 j 列 |
M[:, j] |
| 水平拼接 | M = [a b] 或 M = hcat(a, b) |
| 竖直拼接 | M = [a ; b] 或 M = vcat(a, b) |
| 矩阵转置 | transpose(M) |
| 共轭转置 | M 或 adjoint(M) |
| 迹(trace) | tr(M) |
| 行列式 | det(M) |
| 秩(rank) | rank(M) |
| 特征值 | eigvals(M) |
| 特征向量 | eigvecs(M) |
| 矩阵求逆 | inv(M) |
解矩阵方程 M*x == v |
M\\v 比 inv(M)*v 更好。 |
| 求 Moore-Penrose 伪逆 | pinv(M) |
Julia 有内置的矩阵分解函数。
Julia 会试图推断矩阵是否为特殊矩阵(对称矩阵、厄米矩阵等),但有时会失败。 为了帮助 Julia 分派最优的算法,可以声明矩阵具有特殊的结构。如:对称矩阵、厄密矩阵(Hermitian)、上三角矩阵、下三角矩阵、对角矩阵等。
控制流与循环
| 条件语句 | if-elseif-else-end |
for 循环 |
for i in 1:10println(i)end |
| 嵌套循环 | for i in 1:10, j = 1:5println(i*j)end |
| 枚举 | for (idx, val) in enumerate(arr)println(the $idx-th element is $val)end |
while 循环 |
while bool_expr# 做点啥end |
| 退出循环 | break |
| 退出本次循环 | continue |
函数
函数的所有参数都是传参(passed by reference)。
以 ! 结尾的函数会改变至少一个参数,一般是改变第一个参数:sort!(arr)。
必须的参数以逗号分隔,通过位置传入。
可选的参数均需要一个默认值,用 = 定义。
关键字参数使用名称标记,它们放在函数签名中的分号后面:
function func(req1, req2; key1=dflt1, key2=dflt2)
# 做点啥
end
在调用函数时,关键字参数前的分号 不是 必须的。
return 语句是可选的,但我们强烈建议你为每一个函数都加上 return。
在单一的 return 语句中,可以通过元组返回多种数据结构。
从命令行输入的参数 julia script.jl arg1 arg2...,可以通过常量 ARGS 访问:
for arg in ARGS
println(arg)
end
匿名函数可以用于收集函数(collection functions)或列表推断(list comprehensions): x -> x^2.
函数可以接收可变数量的参数
function func(a...)
println(a)
end
func(1, 2, [3:5]) # tuple: (1, 2, UnitRange{Int64}[3:5])
函数可以嵌套
function outerfunction()
# 在外面做点啥
function innerfunction()
# 在内面做点啥
# 可以访问之前在外部定义的东西
end
# 在外面继续做点啥
end
函数可以显示指定返回类型
# 接收 Number 的任何子类型,返回一个字符串
function stringifynumber(num::T)::String where T <: Number
return $num
end
函数可以通过点语法 向量化
# 这里通过点语法广播了减去均值的操作
julia> using Statistics
julia> A = rand(3, 4);
julia> B = A .- mean(A, dims=1)
3×4 Array{Float64,2}:
0.0387438 0.112224 -0.0541478 0.455245
0.000773337 0.250006 0.0140011 -0.289532
-0.0395171 -0.36223 0.0401467 -0.165713
julia> mean(B, dims=1)
1×4 Array{Float64,2}:
-7.40149e-17 7.40149e-17 1.85037e-17 3.70074e-17
Julia 会根据数据类型生成相应的 特化版本函数。 当一个函数再次以同样的参数调用时,Julia 会跳过编译过程, 直接查找对应的本地机器码(native machine code)。
从 Julia 0.5 之后,定义有潜在歧义是可接受的。 但实际上调用一个不明确的方法是一种 直接错误(immediate error)。
当递归函数的调用很深之后会发生栈溢出(Stack overflow)。 Trampolining 可以用来做尾递归优化,实际上 Julia 还不能 自动完成这种优化。 或者你也可以将尾递归重写为迭代。
字典
| 字典 | d = Dict(key1 => val1, key2 => val2, ...)d = Dict(:key1 => val1, :key2 => val2, ...) |
| 所有的键 (迭代器) | keys(d) |
| 所有的值 (迭代器) | values(d) |
| 按键值对迭代 | for (k,v) in dprintln(key: $k, value: $v)end |
是否存在键 :k |
haskey(d, :k) |
| 将键/值复制到数组 | arr = collect(keys(d))arr = [k for (k,v) in d] |
字典是可变的。当使用符号(:symbol)作为键时,键不可变。
集合
| 声明集合 | s = Set([1, 2, 3, Some text]) |
并集 s1 ∪ s2 |
union(s1, s2) |
交集 s1 ∩ s2 |
intersect(s1, s2) |
补集 s1 ∖ s2 |
setdiff(s1, s2) |
对称差 s1 △ s2(symmetric difference) |
symdiff(s1, s2) |
子集? s1 ⊆ s2 |
issubset(s1, s2) |
检查元素是否在集合(set)内可以在 O(1) 的时间内完成。
收集相关函数
| 将 f 应用到 coll 中的每一个元素上 | map(f, coll) 或map(coll) do elem# 处理 elem# 必须有返回值end |
| 滤出 coll 中使 f 为真的每一个元素 | filter(f, coll) |
| 列表推导 | arr = [f(elem) for elem in coll] |
类型
Julia 没有类,因此也没有类相关的方法。
类型就像是没有方法的类。
抽象类型可以作为子类型,但不能被实例化。
具体的类型不能作为子类型。
struct 默认是不可变的。
不可变类型能能改善程序的性能,并且它们是线程安全的,因为它们在跨线程使用时不需要同步。
可能是一组类型之一的对象称为 Union (联合)类型。
| 类型注释 | var::TypeName |
| 类型声明 | struct Programmername::Stringbirth_year::UInt16fave_language::AbstractStringend |
| 可变类型声明 | 将 struct 替换为 mutable struct |
| 类型别名 | const Nerd = Programmer |
| 类型构造器 | methods(TypeName) |
| 类型实例 | me = Programmer(Ian, 1984, Julia)me = Nerd(Ian, 1984, Julia) |
| 子类型声明 | abstract type Bird endstruct Duck <: Birdpond::Stringend |
| 参数化类型 |
|
| 联合类型 | Union{Int, String} |
| 遍历类型层级 | supertype(TypeName) 和 subtypes(TypeName) |
| 默认的超类型 | Any |
| 所有字段 | fieldnames(TypeName) |
| 所有字段类型 | TypeName.types |
当使用 内部 构造器定义类型时,默认的 外部 构造器就不能用了,如果你还想用它就需要手工定义一下。 内部构造器非常适合于检查参数是否符合特定的(不变的)条件。 当然,可以通过直接访问并修改这些字段来改变这些不变量,除非类型定义为不可变。 关键字 new 可以用于创建相同类型的对象。
类型参数是不可变的,这意味着即使 Float64 <: Real,Point{Float64} <: Point{Real} 依旧为假 对于元组类型(Tuple)正好相反,它们是协变的(covariant): Tuple{Float64} <: Tuple{Real}
通过 code_typed() 函数可以查看 Julia 代码经过类型推断后的内部表示形式(IR)。 这个函数常用来确定本地代码中那些地方出现了 Any 类型而不是特定的类型。
缺失值与空值
| 空值(Null) | nothing |
| 缺失数据 | missing |
| 浮点数的非数值 | NaN |
| 滤除缺失值 | collect(skipmissing([1, 2, missing])) == [1,2] |
| 替换缺失值 | collect((df[:col], 1)) |
| 检查是否有缺失值 | ismissing(x) 而不是 x == missing |
异常处理
| 抛出异常 SomeExcep | throw(SomeExcep()) |
| 再次引发当前的异常 | rethrow() |
| 定义新异常 NewExcep |
|
| 抛出带文本的异常 | error(msg) |
| 异常处理流程 | try# 进行一些可能会失败的操作catch exif isa(ex, SomeExcep)# 处理异常 SomeExcepelseif isa(ex, AnotherExcep)# 处理另一个异常 AnotherExcepelse# 处理其余的异常endfinally# 永远执行这些语句end |
模块
模块是独立的全局变量工作区,它们将类似的功能组合到一起。
| 定义 | module PackageName# 添加模块定义# 使用 export 让定义对外可见end |
包含文件filename.jl |
include(filename.jl) |
| 加载 | using ModuleName # 导出所有名称using ModuleName: x, y # 仅导出 x, yusing ModuleName.x, ModuleName.y: # 仅导出 x, yimport ModuleName # 仅导出 ModuleNameimport ModuleName: x, y # 仅导出 x, yimport ModuleName.x, ModuleName.y # 仅导出 x, y |
| 导出 |
|
using 和 import 只有一点区别:
使用 using 时,你需要写 function Foo.bar(.. 来给 Foo模块的函数 bar 增添一个新方法; 而使用 import Foo.bar 时,只需写 function bar(... 就能达到同样的效果。
表达式
Julia 具有同像性:程序被表示为语言本身的数据结构。 实际上 Julia 语言里的任何东西都是一个表达式 Expr。
符号(Symbols)即 驻留字符串 ,以冒号 : 为前缀。 相对于其他类型来说,符号效率更高。它也经常用作标识符、字典的键或者数据表里的列名。 符号不能进行拼接。
使用引用 :( ... ) 或块引用 quote ... end 可以创建一个表达式,就像 parse(str),和 Expr(:call, ...)。
x = 1
line = 1 + $x # 一些代码
expr = Meta.parse(line) # 生成一个 Expr 对象
typeof(expr) == Expr # true
dump(expr) # 打印生成抽象语法(AST)
eval(expr) == 2 # 对 Expr 对象求值: true
宏
宏允许你在程序中自动生成代码(如:表达式)。
| 定义 | macro macroname(expr)# 做点啥end |
| 使用 | macroname(ex1, ex2, ...) 或 @macroname ex1, ex2, ... |
| 内置的宏 |
|
创建 卫生宏 (hygienic macros)的规则:
- 在宏的内部只通过
local声明本地变量。 - 在宏的内部不使用
eval。 - 转义插值表达式以避免宏变大:
$(esc(expr))
并行计算
并行计算相关的工具可以在标准库 Distributed 里找到。
| 启动带 N 各 worker 的 REPL | julia -p N |
| 可用的 worker 数量 | nprocs() |
| 添加 N 个 worker | addprocs(N) |
| 查看所有 worker 的 pid | for pid in workers()println(pid)end |
| 获得正在执行的 worker 的 id | myid() |
| 移除 worker | rmprocs(pid) |
| 在特定 pid 的 worker 上运行 f(args) | r = remotecall(f, pid, args...)# 或:r = @spawnat pid f(args)...fetch(r) |
| 在特定 pid 的 worker 上运行 f(args) (更高效) | remotecall_fetch(f, pid, args...) |
| 在任意 worker 上运行 f(args) | r = @spawn f(args) ... fetch(r) |
| 在所有 worker 上运行 f(args) | r = [@spawnat w f(args) for w in workers()] ... fetch(r) |
| 让表达式 expr 在所有 worker 上执行 | @everywhere expr |
| 并行化带规约函数 red 的循环 | sum = @distributed (red) for i in 1:10^6# 进行并行任务end |
| 将 f 用用到集合 coll 中的所有元素上 | pmap(f, coll) |
Worker 就是人们所说的并行/并发的进程。
需要并行化的模块,最好拆分成包含所有功能与变量的函数文件,和一个用于处理数据的驱动文件。 很明显驱动文件需要导入函数文件。
一个有实际意义的规约函数的例子:单词计数 by Adam DeConinck.
输入/输出
| 读取流 | stream = stdinfor line in eachline(stream)# 做点啥end |
| 读取文件 | open(filename) do filefor line in eachline(file)# 做点啥endend |
| 读取 CSV 文件 | using CSVdata = CSV.File(filename) |
| 写入 CSV 文件 | using CSVCSV.write(filename, data) |
| 保存 Julia 对象 | using JLDsave(filename, object_key, object, ...) |
| 读取 Julia 对象 | using JLDd = load(filename) # 返回对象的字典 |
| 保存 HDF5 | using HDF5h5write(filename, key, object) |
| 读取 HDF5 | using HDF5h5read(filename, key) |
DataFrames
想要类似 dplyr 的工具,请使用 DataFramesMeta.jl.
| 读取 Stata, SPSS, 等文件 | using StatFiles |
| 描述(describe) data frame | describe(df) |
得到 col 列的向量 |
v = df[:col] |
按 col 排序 |
sort!(df, [:col]) |
分类(Categorical) col |
categorical!(df, [:col]) |
列出 col 的级别 |
levels(df[:col]) |
所有满足 col==val 的结果 |
df[df[:col] .== val, :] |
| 从宽格式转换为长格式 | stack(df, [1:n; ])stack(df, [:col1, :col2, ...])melt(df, [:col1, :col2]) |
| 从长格式转换为宽格式 | unstack(df, :id, :val) |
让表格可以有空值 Nullable |
allowmissing!(df) 或 allowmissing!(df, :col) |
| 在行上迭代 | for r in eachrow(df)# 干点啥# r 是带属性的行名end |
| 在列上迭代 | for c in eachcol(df)# 干点啥# c 是列名和列向量的元组end |
| 将函数应用到组 | by(df, :group_col, func) |
| 查询 | using Queryquery = @from r in df begin@where r.col1 > 40@select {new_name=r.col1, r.col2}@collect DataFrame # 默认的迭代器end |
自我检查与反射
| 类型 | typeof(name) |
| 类型检查 | isa(name, TypeName) |
| 列出子类型 | subtypes(TypeName) |
| 列出超类型 | supertype(TypeName) |
| 函数方法 | methods(func) |
| 即时编译的字节码 | code_llvm(expr) |
| 汇编代码 | code_native(expr) |
值得关注的包与项目
许多核心包都是由不同的社区来管理。这些社区以 Julia + [Topic] 的形式命名。
| 统计 | Julia Statistics |
| 微分方程 | Julia DiffEq |
| 自动微分 | Julia Diff |
| 数值优化 | Julia Opt |
| 绘图 | Julia Plots |
| 网络(图)分析 | Julia Graphs |
| Web | Julia Web |
| 地理空间 | Julia Geo |
| 机器学习 | Julia ML |
超级常用的包
| DataFrames.jl | 线性/逻辑斯蒂(logistic)回归 |
| Distributions.jl | 统计分布 |
| Flux.jl | 机器学习 |
| Gadfly.jl | 类 ggplot2 的画图包 |
| Graphs.jl | 网络分析 |
| TextAnalysis.jl | 自然语言处理(NLP) |
命名规范
- Julia 代码风格主要的约定是:尽量避免使用下划线,除非不用就难于理解。
- 变量名小写或使用蛇形命名(snake_case):
somevariable。 - 常数全部大写:
SOMECONSTANT。 - 函数名小写或使用蛇形命名(snake_case):
somefunction。 - 宏小写或使用蛇形命名(snake_case):
@somemacro。 - 类型名用首字母大写的驼峰命名:
SomeType。 - Julia 代码文件以
.jl为后缀。
更详细的代码风格规范请参阅手册:代码风格指南
性能改进建议
- 编写 类型稳定 的代码
- 尽可能使用不可变类型
- 大数组用
sizehint!预分配内存 - 用
arr = nothing释放大数组的内存 - 使用列访问数组,因为多维数组总是以列优先的顺序储存
- 预分配储存结果用的数据结构
- 在实时应用中使用
disable_gc()关闭垃圾收集器 - 避免使用关键字参数的 splat 操作符(
...) - 使用会改变参数的 APIs 以避免复制数据结构。(例如:以
!结尾的函数) - 使用逐元素的数组操作,而不是列表推断(list comprehensions)
- 避免在计算密集的循环中使用
try–catch - 避免在收集(collections)中出现
Any - 避免在收集(collections)中使用抽象类型
- 避免在 I/O 中使用字符串插值
- 不像 R, MATLAB 或 Python,在 Julia 中向量化 并不会提升运行速度
- 避免在运行时使用
eval








请登录后查看评论内容