与其他语言的显著差异
与 MATLAB 的显著差异
虽然 MATLAB 用户可能会发现 Julia 的语法很熟悉,但 Julia 不是 MATLAB 的克隆。 它们之间存在重大的语法和功能差异。 以下是一些可能会使习惯于 MATLAB 的 Julia 用户感到困扰的显著差异:
- Julia 数组使用方括号
A[i,j]
进行索引。 - Julia 的数组在赋值给另一个变量时不发生复制。执行
A = B
后,改变B
中元素也会修改A
。 - Julia 的值在向函数传递时不发生复制。如果某个函数修改了数组,这一修改对调用者是可见的。
- Julia 不会在赋值语句中自动增长数组。 而在 MATLAB 中
a(4) = 3.2
可以创建数组a = [0 0 0 3.2]
,而a(5) = 7
可以将它增长为a = [0 0 0 3.2 7]
。如果a
的长度小于 5 或者这个语句是第一次使用标识符a
,则相应的 Julia 语句a[5] = 7
会抛出错误。Julia 使用push!
和append!
来增长Vector
,它们比 MATLAB 的a(end+1) = val
更高效。 - 虚数单位
sqrt(-1)
在 Julia 中表示为im
,而不是在 MATLAB 中的i
或j
。 - 在 Julia 中,没有小数点的数字字面量(例如
42
)会创建整数而不是浮点数。也支持任意大整数字面量。因此,某些操作(如2^-1
)将抛出 domain error,因为结果不是整数(有关的详细信息,请参阅常见问题中有关 domain errors 的条目)。 point numbers. As a result, some operations can throw a domain error if they expect a float; for example,julia> a = -1; 2^a
throws a domain error, as the result is not an integer (see the FAQ entry on domain errors for details). - 在 Julia 中,能返回多个值并将其赋值为元组,例如
(a, b) = (1, 2)
或a, b = 1, 2
。 在 Julia 中不存在 MATLAB 的nargout
,它通常在 MATLAB 中用于根据返回值的数量执行可选工作。取而代之的是,用户可以使用可选参数和关键字参数来实现类似的功能。 - Julia 拥有真正的一维数组。列向量的大小为
N
,而不是Nx1
。例如,rand(N)
创建一个一维数组。 - 在 Julia 中,
[x,y,z]
将始终构造一个包含x
、y
和z
的 3 元数组。- 要在第一个维度(「垂直列」)中连接元素,请使用
vcat(x,y,z)
或用分号分隔([x; y; z]
)。 - 要在第二个维度(「水平行」)中连接元素,请使用
hcat(x,y,z)
或用空格分隔([x y z]
)。 - 要构造分块矩阵(在前两个维度中连接元素),请使用
hvcat
或组合空格和分号([a b; c d]
)。
- 要在第一个维度(「垂直列」)中连接元素,请使用
- 在 Julia 中,
a:b
和a:b:c
构造AbstractRange
对象。使用collect(a:b)
构造一个类似 MATLAB 中完整的向量。通常,不需要调用collect
。在大多数情况下,AbstractRange
对象将像普通数组一样运行,但效率更高,因为它是懒惰求值。这种创建专用对象而不是完整数组的模式经常被使用,并且也可以在诸如range
之类的函数中看到,或者在诸如enumerate
和zip
之类的迭代器中看到。特殊对象大多可以像正常数组一样使用。 - Julia 中的函数返回其最后一个表达式或
return
关键字的值而无需在函数定义中列出要返回的变量的名称(有关详细信息,请参阅 return 关键字)。 - Julia 脚本可以包含任意数量的函数,并且在加载文件时,所有定义都将在外部可见。可以从当前工作目录之外的文件加载函数定义。
- 在 Julia 中,例如
sum
、prod
和max
的归约操作会作用到数组的每一个元素上,当调用时只有一个函数,例如sum(A)
,即使A
并不只有一个维度。 - 在 Julia 中,调用无参数的函数时必须使用小括号,例如
rand()
。 - Julia 不鼓励使用分号来结束语句。语句的结果不会自动打印(除了在 REPL 中),并且代码的一行不必使用分号结尾。
println
或者@printf
能用来打印特定输出。 - 在 Julia 中,如果
A
和B
是数组,像A == B
这样的逻辑比较运算符不会返回布尔值数组。相反地,请使用A .== B
。对于其他的像是<
、>
的布尔运算符同理。 - 在 Julia 中,运算符
&
、|
和⊻
(xor
)进行按位操作,分别与MATLAB中的and
、or
和xor
等价,并且优先级与 Python 的按位运算符相似(不像 C)。他们可以对标量运算或者数组中逐元素运算,可以用来合并逻辑数组,但是注意运算顺序的区别:括号可能是必要的(例如,选择A
中等于 1 或 2 的元素可使用(A .== 1) .| (A .== 2)
)。 - 在 Julia 中,集合的元素可以使用 splat 运算符
...
来作为参数传递给函数,如xs=[1,2]; f(xs...)
。 - Julia 的
svd
将奇异值作为向量而非密集对角矩阵返回。 - 在 Julia 中,
...
不用于延续代码行。不同的是,Julia 中不完整的表达式会自动延续到下一行。 - 在 Julia 和 MATLAB 中,变量
ans
被设置为交互式会话中提交的最后一个表达式的值。在 Julia 中与 MATLAB 不同的是,当 Julia 代码以非交互式模式运行时并不会设置ans
。 - Julia 的
struct
不支持在运行时动态地添加字段,这与 MATLAB 的class
不同。如需支持,请使用Dict
。 - 在 Julia 中,每个模块有自身的全局作用域/命名空间,而在 MATLAB 中只有一个全局作用域。
- 在 MATLAB 中,删除不需要的值的惯用方法是使用逻辑索引,如表达式
x(x>3)
或语句x(x>3) = []
来 in-place 修改x
。相比之下,Julia 提供了更高阶的函数filter
和filter!
,允许用户编写filter(z->z>3, x)
和filter!(z->z>3, x)
来代替相应直译x[x.>3]
和x = x[x.>3]
。使用filter!
可以减少临时数组的使用。 - 类似于提取(或「解引用」)元胞数组的所有元素的操作,例如 MATLAB 中的
vertcat(A{:})
,在 Julia 中是使用 splat 运算符编写的,例如vcat(A...)
。 - In Julia, the
adjoint
function performs conjugate transposition; in MATLAB,adjoint
provides the “adjugate” or classical adjoint, which is the transpose of the matrix of cofactors.
与 R 的显著差异
Julia 的目标之一是为数据分析和统计编程提供高效的语言。对于从 R 转到 Julia 的用户来说,这是一些显著差异:
Julia 的单引号封闭字符,而不是字符串。
Julia 可以通过索引字符串来创建子字符串。在 R 中,在创建子字符串之前必须将字符串转换为字符向量。
在 Julia 中,与 Python 相同但与 R 不同的是,字符串可由三重引号
""" ... """
创建。此语法对于构造包含换行符的字符串很方便。在 Julia 中,可变参数使用 splat 运算符
...
指定,该运算符总是跟在具体变量的名称后面,与 R 的不同,R 的...
可以单独出现。在 Julia 中,模数是
mod(a, b)
,而不是a %% b
。Julia 中的%
是余数运算符。在 Julia 中,并非所有数据结构都支持逻辑索引。此外,Julia 中的逻辑索引只支持长度等于被索引对象的向量。例如:
- 在 R 中,
c(1, 2, 3, 4)[c(TRUE, FALSE)]
等价于c(1, 3)
。 - 在 R 中,
c(1, 2, 3, 4)[c(TRUE, FALSE, TRUE, FALSE)]
等价于c(1, 3)
。 - 在 Julia 中,
[1, 2, 3, 4][[true, false]]
抛出BoundsError
。 - 在 Julia 中,
[1, 2, 3, 4][[true, false, true, false]]
产生[1, 3]
。
- 在 R 中,
与许多语言一样,Julia 并不总是允许对不同长度的向量进行操作,与 R 不同,R 中的向量只需要共享一个公共的索引范围。例如,
c(1, 2, 3, 4) + c(1, 2)
是有效的 R,但等价的[1, 2, 3, 4] + [1, 2]
在 Julia 中会抛出一个错误。在逗号不改变代码含义时,Julia 允许使用可选的尾随括号。在索引数组时,这可能在 R 用户间造成混淆。例如,R 中的
x[1,]
将返回矩阵的第一行;但是,在 Julia 中,引号被忽略,于是x[1,] == x[1]
,并且将返回第一个元素。要提取一行,请务必使用:
,如x[1,:]
。Julia 的
map
首先接受函数,然后是该函数的参数,这与 R 中的lapply(<structure>, function, ...)
不同。类似地,R 中的apply(X, MARGIN, FUN, ...)
等价于 Julia 的mapslices
,其中函数是第一个参数。R 中的多变量 apply,如
mapply(choose, 11:13, 1:3)
,在 Julia 中可以编写成broadcast(binomial, 11:13, 1:3)
。等价地,Julia 提供了更短的点语法来向量化函数binomial.(11:13, 1:3)
。Julia 使用
end
来表示条件块(如if
)、循环块(如while
/for
)和函数的结束。为了代替单行if ( cond ) statement
,Julia 允许形式为if cond; statement; end
、cond && statement
和!cond || statement
的语句。后两种语法中的赋值语句必须显式地包含在括号中,例如cond && (x = value)
,这是因为运算符的优先级。在 Julia 中,
<-
,<<-
和->
不是赋值运算符。Julia 的
->
创建一个匿名函数。Julia 使用括号构造向量。Julia 的
[1, 2, 3]
等价于 R 的c(1, 2, 3)
。Julia 的
*
运算符可以执行矩阵乘法,这与 R 不同。如果A
和B
都是矩阵,那么A * B
在 Julia 中表示矩阵乘法,等价于 R 的A %*% B
。在 R 中,相同的符号将执行逐元素(Hadamard)乘积。要在 Julia 中使用逐元素乘法运算,你需要编写A .* B
。Julia 使用
transpose
函数来执行矩阵转置,使用'
运算符或adjoint
函数来执行共轭转置。因此,Julia 的transpose(A)
等价于 R 的t(A)
。另外,Julia 中的非递归转置由permutedims
函数提供。Julia 在编写
if
语句或for
/while
循环时不需要括号:请使用for i in [1, 2, 3]
代替for (int i=1; i <= 3; i++)
,以及if i == 1
代替if (i == 1)
Julia 不把数字
0
和1
视为布尔值。在 Julia 中不能编写if (1)
,因为if
语句只接受布尔值。相反,可以编写if true
、if Bool(1)
或if 1==1
。Julia 不提供
nrow
和ncol
。相反,请使用size(M, 1)
代替nrow(M)
以及size(M, 2)
代替ncol(M)
Julia 仔细区分了标量、向量和矩阵。在 R 中,
1
和c(1)
是相同的。在 Julia 中,它们不能互换地使用。Julia 赋值操作的左侧不能为函数调用的结果:你不能编写
diag(M) = fill(1, n)
。Julia 不鼓励使用函数填充主命名空间。Julia 的大多数统计功能都可在 JuliaStats 组织的包中找到。例如:
- 与概率分布相关的函数由 Distributions 包提供。
- DataFrames 包提供数据帧。
- 广义线性模型由 GLM 包提供。
Julia 提供了元组和真正的哈希表,但不提供 R 风格的列表。在返回多个项时,通常应使用元组或具名元组:请使用
(1, 2)
或(a=1, b=2)
代替list(a = 1, b = 2)
。Julia 鼓励用户编写自己的类型,它比 R 中的 S3 或 S4 对象更容易使用。Julia 的多重派发系统意味着
table(x::TypeA)
和table(x::TypeB)
类似于 R 的table.TypeA(x)
和table.TypeB(x)
。Julia 的值在向函数传递时不发生复制。如果某个函数修改了数组,这一修改对调用者是可见的。这与 R 非常不同,允许新函数更高效地操作大型数据结构。
在 Julia 中,向量和矩阵使用
hcat
、vcat
和hvcat
拼接,而不是像在 R 中那样使用c
、rbind
和cbind
。在 Julia 中,像
a:b
这样的 range 不是 R 中的向量简写,而是一个专门的AbstractRange
对象,该对象用于没有高内存开销地进行迭代。要将 range 转换为 vector,请使用collect(a:b)
。Julia 的
max
和min
分别等价于 R 中的pmax
和pmin
,但两者的参数都需要具有相同的维度。虽然maximum
和minimum
代替了 R 中的max
和min
,但它们之间有重大区别。Julia 的
sum
、prod
、maximum
和minimum
与它们在 R 中的对应物不同。它们都接受一个可选的关键字参数dims
,它表示执行操作的维度。例如,在 Julia 中令A = [1 2; 3 4]
,在 R 中令B <- rbind(c(1,2),c(3,4))
是与之相同的矩阵。然后sum(A)
得到与sum(B)
相同的结果,但sum(A, dims=1)
是一个包含每一列总和的行向量,sum(A, dims=2)
是一个包含每一行总和的列向量。这与 R 的行为形成了对比,在 R 中,单独的colSums(B)
和rowSums(B)
提供了这些功能。如果dims
关键字参数是向量,则它指定执行求和的所有维度,并同时保持待求和数组的维数,例如sum(A, dims=(1,2)) == hcat(10)
。应该注意的是,没有针对第二个参数的错误检查。在 R 中,高性能需要向量化。在 Julia 中,这几乎恰恰相反:性能最高的代码通常通过去向量化的循环来实现。
Julia 是立即求值的,不支持 R 风格的惰性求值。对于大多数用户来说,这意味着很少有未引用的表达式或列名。
Julia 不支持
NULL
类型。最接近的等价物是nothing
,但它的行为类似于标量值而不是列表。请使用x === nothing
代替is.null(x)
。在 Julia 中,缺失值由
missing
表示,而不是由NA
表示。请使用ismissing(x)
(或者在向量上使用逐元素操作ismissing.(x)
)代替isna(x)
。通常使用skipmissing
代替na.rm=TRUE
(尽管在某些特定情况下函数接受skipmissing
参数)。Julia 缺少 R 中的
assign
或get
的等价物。在 Julia 中,
return
不需要括号。在 R 中,删除不需要的值的惯用方法是使用逻辑索引,如表达式
x[x>3]
或语句x = x[x>3]
来 in-place 修改x
。相比之下,Julia 提供了更高阶的函数filter
和filter!
,允许用户编写filter(z->z>3, x)
和filter!(z->z>3, x)
来代替相应直译x[x.>3]
和x = x[x.>3]
。使用filter!
可以减少临时数组的使用。
与 Python 的显著差异
- Julia 需要用
end
来结束代码块。与 Python 不同,Julia 没有pass
关键字。 - 在 Julia 中,数组、字符串等的索引从 1 开始,而不是从 0 开始。
- Julia 的切片索引包含最后一个元素,这与 Python 不同。Julia 中的
a[2:3]
就是 Python 中的a[1:3]
。 - Julia 不支持负数索引。特别地,列表或数组的最后一个元素在 Julia 中使用
end
索引,而不像在 Python 中使用-1
。 - Julia 的
for
、if
、while
等代码块由end
关键字终止。缩进级别并不像在 Python 中那么重要。 - Julia 没有用来续行的语法:如果在行的末尾,到目前为止的输入是一个完整的表达式,则认为其已经结束;否则,认为输入继续。强制表达式继续的一种方式是将其包含在括号中。
- 默认情况下,Julia 数组是列优先的(Fortran 顺序),而 NumPy 数组是行优先(C 顺序)。为了在循环数组时获得最佳性能,循环顺序应该在 Julia 中相对于 NumPy 反转(请参阅 Performance Tips 中的对应章节)。be reversed in Julia relative to NumPy (see relevant section of Performance Tips).
- Julia 的更新运算符(例如
+=
,-=
,···)是 not in-place,而 Numpy 的是。这意味着A = [1, 1]; B = A; B += [3, 3]
不会改变A
中的值,而将名称B
重新绑定到右侧表达式B = B + 3
的结果,这是一个新的数组。对于 in-place 操作,使用B .+= 3
(另请参阅 dot operators)、显式的循环或者InplaceOps.jl
。 - 每次调用方法时,Julia 都会计算函数参数的默认值,不像在 Python 中,默认值只会在函数定义时被计算一次。例如,每次无输入参数调用时,函数
f(x=rand()) = x
都返回一个新的随机数在另一方面,函数g(x=[1,2]) = push!(x,3)
在每次以g()
调用时返回[1,2,3]
。 - 在 Julia 中,
%
是余数运算符,而在 Python 中是模运算符。 - The commonly used
Int
type corresponds to the machine integer type (Int32
orInt64
). This means it will overflow, such that2^64 == 0
. If you need larger values use another appropriate type, such asInt128
,BigInt
or a floating point type likeFloat64
.
与 C/C++ 的显著差异
- Julia 的数组由方括号索引,方括号中可以包含不止一个维度
A[i,j]
。这样的语法不仅仅是像 C/C++ 中那样对指针或者地址引用的语法糖,参见关于数组构造的语法的 Julia 文档(依版本不同有所变动)。 - 在 Julia 中,数组、字符串等的索引从 1 开始,而不是从 0 开始。
- Julia 的数组在赋值给另一个变量时不发生复制。执行
A = B
后,改变B
中元素也会修改A
。像+=
这样的更新运算符不会以 in-place 的方式执行,而是相当于A = A + B
,将左侧绑定到右侧表达式的计算结果上。 - Julia 的数组是列优先的(Fortran 顺序),而 C/C++ 的数组默认是行优先的。要使数组上的循环性能最优,在 Julia 中循环的顺序应该与 C/C++ 相反(参见 性能建议)。reversed in Julia relative to C/C++ (see relevant section of Performance Tips).
- Julia 的值在赋值或向函数传递时不发生复制。如果某个函数修改了数组,这一修改对调用者是可见的。
- 在 Julia 中,空格是有意义的,这与 C/C++ 不同,所以向 Julia 程序中添加或删除空格时必须谨慎。
- 在 Julia 中,没有小数点的数值字面量(如
42
)生成有符号整数,类型为Int
,但如果字面量太长,超过了机器字长,则会被自动提升为容量更大的类型,例如Int64
(如果Int
是Int32
)、Int128
,或者任意精度的BigInt
类型。不存在诸如L
,LL
,U
,UL
,ULL
这样的数值字面量后缀指示无符号和/或有符号与无符号。十进制字面量始终是有符号的,十六进制字面量(像 C/C++ 一样由0x
开头)是无符号的。另外,十六进制字面量与 C/C++/Java 不同,也与 Julia 中的十进制字面量不同,它们的类型取决于字面量的长度,包括开头的 0。例如,0x0
和0x00
的类型是UInt8
,0x000
和0x0000
的类型是UInt16
。同理,字面量的长度在 5-8 之间,类型为UInt32
;在 9-16 之间,类型为UInt64
;在 17-32 之间,类型为UInt128
。当定义十六进制掩码时,就需要将这一问题考虑在内,比如~0xf == 0xf0
与~0x000f == 0xfff0
完全不同。64 位Float64
和 32 位Float32
的字面量分别表示为1.0
和1.0f0
。浮点字面量在无法被精确表示时舍入(且不会提升为BigFloat
类型)。浮点字面量在行为上与 C/C++ 更接近。八进制(前缀为0o
)和二进制(前缀为0b
)也被视为无符号的。 - 字符串字面量可用
"
或"""
分隔,用"""
分隔的字面量可以包含"
字符而无需像"\""
这样来引用它。字符串字面量可以包含插入其中的其他变量或表达式,由$variablename
或$(expression)
表示,它在该函数所处的上下文中计算变量名或表达式。 //
表示Rational
数,而非单行注释(其在 Julia 中是#
)#=
表示多行注释的开头,=#
结束之。- Julia 中的函数返回其最后一个表达式或
return
关键字的值。可以从函数中返回多个值并将其作为元组赋值,如(a, b) = myfunction()
或a, b = myfunction()
,而不必像在 C/C++ 中那样必须传递指向值的指针(即a = myfunction(&b)
)。 - Julia 不要求使用分号来结束语句。表达式的结果不会自动打印(除了在交互式提示符中,即 REPL),且代码行不需要以分号结尾。
println
或@printf
可用于打印特定输出。在 REPL 中,;
可用于抑制输出。;
在[ ]
中也有不同的含义,需要注意。;
可用于在单行中分隔表达式,但在许多情况下不是绝对必要的,更经常是为了可读性。 - 在 Julia 中,运算符
⊻
(xor
)执行按位 XOR 操作,即 C/C++ 中的^
。此外,按位运算符不具有与 C/C++ 相同的优先级,所以可能需要括号。 - Julia 的
^
是取幂(pow),而非 C/C++ 中的按位 XOR(在 Julia 中请使用⊻
或xor
) , in Julia) - Julia 中有两个右移运算符,
>>
和>>>
。>>>
执行逻辑移位,>>
总是执行算术移位(译注:此处原文为「>>>
performs an arithmetic shift,>>
always performs a logical shift」,疑误),与 C/C++ 不同,C/C++ 中的>>
的含义依赖于被移位的值的类型。 - Julia 的
->
创建一个匿名函数,它并不通过指针访问成员。 - Julia 在编写
if
语句或for
/while
循环时不需要括号:请使用for i in [1, 2, 3]
代替for (int i=1; i <= 3; i++)
,以及if i == 1
代替if (i == 1)
- Julia 不把数字
0
和1
视为布尔值。在 Julia 中不能编写if (1)
,因为if
语句只接受布尔值。相反,可以编写if true
、if Bool(1)
或if 1==1
。 - Julia 使用
end
来表示条件块(如if
)、循环块(如while
/for
)和函数的结束。为了代替单行if ( cond ) statement
,Julia 允许形式为if cond; statement; end
、cond && statement
和!cond || statement
的语句。后两种语法中的赋值语句必须显式地包含在括号中,例如cond && (x = value)
,这是因为运算符的优先级。 - Julia 没有用来续行的语法:如果在行的末尾,到目前为止的输入是一个完整的表达式,则认为其已经结束;否则,认为输入继续。强制表达式继续的一种方式是将其包含在括号中。
- Julia 宏对已解析的表达式进行操作,而非程序的文本,这允许它们执行复杂的 Julia 代码转换。宏名称以
@
字符开头,具有类似函数的语法@mymacro(arg1, arg2, arg3)
和类似语句的语法@mymacro arg1 arg2 arg3
。两种形式的语法可以相互转换;如果宏出现在另一个表达式中,则类似函数的形式尤其有用,并且它通常是最清晰的。类似语句的形式通常用于标注块,如在分布式for
结构中:@distributed for i in 1:n; #= body =#; end
。如果宏结构的结尾不那么清晰,请使用类似函数的形式。 - Julia 有一个枚举类型,使用宏
@enum(name, value1, value2, ...)
来表示,例如:@enum(Fruit, banana=1, apple, pear)
。 - 按照惯例,修改其参数的函数在名称的末尾有个
!
,例如push!
。 - 在 C++ 中,默认情况下,你具有静态分派,即为了支持动态派发,你需要将函数标注为 virtual 函数。另一方面,Julia 中的每个方法都是「virtual」(尽管它更通用,因为方法是在每个参数类型上派发的,而不仅仅是
this
,并且使用的是最具体的声明规则)。
Noteworthy differences from Common Lisp
Julia uses 1-based indexing for arrays by default, and it can also handle arbitrary index offsets.
Functions and variables share the same namespace (“Lisp-1”).
There is a
Pair
type, but it is not meant to be used as aCOMMON-LISP:CONS
. Various iterable collections can be used interchangeably in most parts of the language (eg splatting, tuples, etc).Tuple
s are the closest to Common Lisp lists for short collections of heterogeneous elements. UseNamedTuple
s in place of alists. For larger collections of homogeneous types,Array
s andDict
s should be used.The typical Julia workflow for prototyping also uses continuous manipulation of the image, implemented with the Revise.jl package.
Bignums are supported, but conversion is not automatic; ordinary integers overflow.
Modules (namespaces) can be hierarchical.
import
andusing
have a dual role: they load the code and make it available in the namespace.import
for only the module name is possible (roughly equivalent toASDF:LOAD-OP
). Slot names don’t need to be exported separately. Global variables can’t be assigned to from outside the module (except witheval(mod, :(var = val))
as an escape hatch).Macros start with
@
, and are not as seamlessly integrated into the language as Common Lisp; consequently, macro usage is not as widespread as in the latter. A form of hygiene for macros is supported by the language. Because of the different surface syntax, there is no equivalent toCOMMON-LISP:&BODY
.All functions are generic and use multiple dispatch. Argument lists don’t have to follow the same template, which leads to a powerful idiom (see
do
). Optional and keyword arguments are handled differently. Method ambiguities are not resolved like in the Common Lisp Object System, necessitating the definition of a more specific method for the intersection.Symbols do not belong to any package, and do not contain any values per se.
M.var
evaluates the symbolvar
in the moduleM
.A functional programming style is fully supported by the language, including closures, but isn’t always the idiomatic solution for Julia. Some workarounds may be necessary for performance when modifying captured variables.