Примечательные отличия от других языков
Примечательные отличия от MATLAB
Хотя пользователям MATLAB синтаксис Julia может показаться знакомым, Julia не является клоном MATLAB. Между ними есть важные синтаксические и функциональные различия. Ниже перечислены некоторые примечательные отличия Julia, которые могут сбить с толку пользователей, привыкших к MATLAB.
-
Индексы массивов в Julia указываются в квадратных скобках:
A[i,j]. -
Когда массив в Julia присваивается другой переменной, он не копируется. После присваивания
A = Bизменение элементовBприведет к их изменению вA. Чтобы избежать этого, используйтеA = copy(B). -
В Julia значения, передаваемые функции, не копируются. Если функция изменяет массив, эти изменения будут видны вызывающей стороне.
-
В Julia массивы не увеличиваются автоматически при использовании оператора присваивания. Тогда как в MATLAB с помощью выражения
a(4) = 3.2можно создать массивa = [0 0 0 3.2], а с помощью выраженияa(5) = 7— увеличить его доa = [0 0 0 3.2 7], в Julia соответствующий операторa[5] = 7выдает ошибку, если длинаaменьше 5 или если имяaвпервые используется в этом операторе. В Julia есть функцииpush!иappend!, которые позволяют увеличивать объектыVectorгораздо эффективнее, чемa(end+1) = valв MATLAB. -
Мнимая единица
sqrt(-1)представлена в Julia константойim, а неiилиj, как в MATLAB. -
В Julia числовые литералы без десятичного разделителя (например,
42) создают целые числа, а не числа с плавающей запятой. В результате некоторые операции могут приводить к ошибке выхода за пределы области, если они ожидают число с плавающей запятой. Например,julia> a = -1; 2^aвыдаст такую ошибку, так как результат не целочисленный (подробные сведения см. в разделе FAQ об ошибках выхода за пределы области). -
Если в Julia нужно вернуть или присвоить несколько значений, используются кортежи, например
(a, b) = (1, 2)илиa, b = 1, 2. В Julia нет функцииnargout, которая часто применяется в MATLAB для выполнения дополнительных действий в зависимости от количества возвращенных значений. В аналогичных целях можно использовать необязательные и именованные аргументы. -
В Julia есть истинные одномерные массивы. Векторы столбцов имеют размер
N, а неNx1. Например, выражениеrand(N)создает одномерный массив. -
В Julia
[x,y,z]всегда создает массив, содержащий три элемента:x,yиz.-
Для конкатенации по первому («вертикальному») измерению вызовите функцию
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. Для создания полного вектора, как в MATLAB, используйте методcollect(a:b). Однако обычно вызыватьcollectне нужно. ОбъектAbstractRangeв большинстве случаев ведет себя как обычный массив, однако он эффективнее благодаря «ленивому» вычислению значений. Этот шаблон создания специальных объектов вместо полных массивов применяется часто, в том числе с функциями, такими какrange, и итераторами, такими какenumerateиzip. Специальные объекты, как правило, можно использовать как обычные массивы. -
Функции в Julia возвращают результат вычисления последнего выражения или выражения с ключевым словом
return. Перечислять имена возвращаемых переменных в определении функции не нужно (подробные сведения см. в разделе Ключевое слово return). -
Скрипт в Julia может содержать сколько угодно функций, и все определения видны извне при загрузке файла. Определения функций можно загружать из файлов вне текущего рабочего каталога.
-
В Julia при вызове редукций, таких как
sum,prodиmaximum, с одним аргументом, например , они выполняютсяsum(A)применительно к каждому элементу массива, даже если уAболее одного измерения. -
В Julia при вызове функции без аргументов необходимо использовать круглые скобки, например
rand(). -
В Julia не рекомендуется ставить точку с запятой в конце выражений. Результаты выражений не выводятся на экран автоматически (кроме как в интерактивной командной строке), поэтому строки кода нет необходимости заканчивать точкой с запятой. Для вывода на экран определенных данных можно использовать функцию
printlnили макрос@printf. -
В Julia, если
AиB— массивы, операции логического сравнения, такие какA == B, не возвращают массив логических значений. Вместо этого используйтеA .== Bили аналогичные выражения для других логических операторов, таких как<и>. -
В Julia операторы
&,|и⊻(xor) выполняются побитово, так же, как, соответственно,and,orиxorв MATLAB. Их приоритет такой же, как у побитовых операторов в Python (но не такой, как в C). Они могут применяться к скалярным значениям или поэлементно к массивам, а также использоваться для объединения логических массивов. Однако обратите внимание на различие в очередности операций: могут потребоваться круглые скобки (например, для выбора элементовA, равных 1 или 2, используйте выражение(A .== 1) .| (A .== 2)). -
В Julia элементы коллекции могут передаваться функции как аргументы с помощью оператора расширения (splat-оператора)
..., напримерxs=[1,2]; f(xs...). -
Функция
svdв Julia возвращает сингулярные значения в виде вектора, а не плотной диагональной матрицы. -
В Julia символ
...не применяется для продолжения строк кода. Вместо этого неполное выражение автоматически продолжается в следующей строке. -
Как в Julia, так и в MATLAB переменной
ansприсваивается значение последнего выражения, введенного в рамках интерактивного сеанса. Однако в Julia, в отличие от MATLAB, переменнаяansне задается, когда код на Julia выполняется в рамках неинтерактивного сеанса. -
Структуры (
struct) в Julia не поддерживают динамическое добавление полей во время выполнения, в отличие от классов (class) в MATLAB. Вместо этого используйтеDict. Словари в Julia не упорядочиваются. -
В Julia каждый модуль имеет собственную глобальную область или пространство имен, в то время как в MATLAB глобальная область только одна.
-
В MATLAB идиоматическим способом удаления ненужных значений является использование логического индексирования, как в выражении
x(x>3)или в оператореx(x>3) = []для измененияxна месте. В Julia с этой целью предоставляются функции более высокого порядкаfilterиfilter!, которые позволяют использовать выраженияfilter(z->z>3, x)иfilter!(z->z>3, x)в качестве альтернативы дляx[x.>3]иx = x[x.>3]. Функцияfilter!позволяет реже прибегать к временным массивам. -
Извлечение (или «разыменование») всех элементов массива ячеек, например
vertcat(A{:})в MATLAB, записывается в Julia с помощью оператора расширения (splat-оператор), напримерvcat(A...). -
В Julia функция
adjointпроизводит сопряженное транспонирование; в MATLABadjointпредоставляет присоединенную матрицу (классическое сопряжение), то есть выполняет транспонирование матрицы алгебраических дополнений. -
В Julia выражение a^b^c вычисляется как a^(b^c), в то время как в MATLAB — как (a^b)^c.
Примечательные отличия от R
Одной из целей создания языка Julia было обеспечить эффективные возможности для анализа данных и статистического программирования. Тем, кто переходит с языка R на Julia, следует помнить о следующих важных отличиях.
-
В Julia в одинарные кавычки заключаются символы, а не строки.
-
В Julia подстроки можно создавать по индексам строки. В R перед созданием подстрок строки необходимо преобразовывать в векторы символов.
-
В Julia, так же как в Python, но не так, как в R, строки можно создавать с помощью тройных кавычек
""" ... """. Этот синтаксис удобен для создания строк, содержащих разрывы. -
В Julia переменное количество аргументов указывается с помощью оператора расширения
..., который обязательно должен стоять после имени определенной переменной. В R, напротив,...можно использовать отдельно. -
В Julia остаток от целочисленного деления вычисляется с помощью
mod(a, b), а неa %% b.%в Julia — это оператор нахождения остатка. -
В Julia векторы создаются с помощью квадратных скобок.
[1, 2, 3]в Julia — это то же самое, чтоc(1, 2, 3)в R. -
В 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]вызывает исключениеBoundsError.
-
-
Как и во многих других языках, в Julia разрешены не все операции с векторами разной длины, в отличие от R, где достаточно, чтобы у векторов был общий диапазон индексов. Например, выражение
c(1, 2, 3, 4) + c(1, 2)допустимо в R, но эквивалентное выражение[1, 2, 3, 4] + [1, 2]в Julia вызовет ошибку. -
В Julia допускается необязательная запятая в конце, если она не меняет смысл кода. Пользователей R это может сбить с толку при обращении к элементам массивов по индексам. Например, выражение
x[1,]в R возвращает первую строку матрицы, однако в Julia запятая игнорируется, поэтомуx[1,] == x[1]и возвращает первый элемент. Чтобы получить строку, используйте:, как в выраженииx[1,:]. -
Функция
mapв Julia сначала принимает функцию, а затем ее аргументы, в отличие отlapply(<structure>, function, ...)в R. Точно так же эквивалентом функции Rapply(X, MARGIN, FUN, ...)в Julia будет функцияmapslices, в которую другая функция передается первым аргументом. -
Многовариантное применение в R, например
mapply(choose, 11:13, 1:3), можно записать в Julia в видеbroadcast(binomial, 11:13, 1:3). Кроме того, для функций векторизацииbinomial.(11:13, 1:3)в Julia предлагается синтаксис через точку. -
Для обозначения конца условных блоков, таких как
if, блоков циклов, таких как иwhile/for, и функций в Julia используется ключевое словоend. Вместо однострочной записиif ( cond ) statementв Julia допустимы операторы видаif cond; statement; end,cond && statementи!cond || statement. Операторы присваивания в двух последних синтаксических конструкциях должны явным образом заключаться в круглые скобки, напримерcond && (x = value). -
В Julia
<-,<<-и->не являются операторами присваивания. -
Оператор
->в Julia создает анонимную функцию. -
Оператор
*в Julia позволяет перемножать матрицы, в отличие от R. ЕслиAиB— матрицы, тоA * Bв Julia означает умножение матриц. В R для этого требуется выражениеA %% B. В R такая запись означает поэлементное (адамарово) произведение. В Julia для поэлементного умножения необходимо написатьA . B. -
В Julia для транспонирования матриц служит функция
transpose, а для сопряженного транспонирования — оператор'или функцияadjoint. Таким образом, выражениеtranspose(A)в Julia равносильноt(A)в R. Кроме того, для нерекурсивного транспонирования в Julia есть функцияpermutedims. -
В Julia при написании операторов
ifили цикловforиwhileкруглые скобки не требуются: вместоfor i in [1, 2, 3]достаточно написатьfor (i in c(1, 2, 3)), а вместоif i == 1—if (i == 1). -
В Julia числа
0и1не интерпретируются как логические значения. Написатьif (1)в Julia нельзя, так как операторifпринимает только логические значения. Вместо этого можно написатьif true,if Bool(1)илиif 1==1. -
В Julia нет функций
nrowиncol. Вместоnrow(M)используйтеsize(M, 1), а вместоncol(M)—size(M, 2). -
В Julia скалярные значения, векторы и матрицы четко различаются. В R
1иc(1)— это одно и то же. В Julia же это не взаимозаменяемые выражения. -
В Julia нельзя присвоить результат вызову функции в левой части операции присваивания, то есть нельзя написать
diag(M) = fill(1, n). -
В Julia не рекомендуется вводить лишние функции в главное пространство имен. Большинство статистических функций Julia находятся в пакетах, предлагаемых группой JuliaStats. Например:
-
Функции, относящиеся к вероятностным распределениям, входят в пакет Distributions.
-
Пакет DataFrames предоставляет инструменты для работы с Data Frame.
-
Обобщенные линейные модели предоставляются пакетом GLM.
-
-
В Julia имеются кортежи и настоящие хэш-таблицы, но нет списков в стиле R. Для возврата нескольких элементов обычно следует использовать кортеж или именованный кортеж: вместо
list(a = 1, b = 2)используйте(1, 2)или(a=1, b=2). -
Julia поощряет пользователей к написанию собственных типов, которые проще в использовании, чем объекты S3 или S4 в R. Система множественной диспетчеризации в Julia означает, что выражения
table(x::TypeA)иtable(x::TypeB)работают так же, какtable.TypeA(x)иtable.TypeB(x)в R. -
В Julia присваиваемые или передаваемые в функцию значения не копируются. Если функция изменяет массив, эти изменения будут видны вызывающей стороне. Это серьезное отличие от R, которое позволяет новым функциям оперировать с большими структурами данных гораздо эффективнее.
-
В Julia конкатенация векторов и матриц производится с помощью функций
hcat,vcatиhvcat, а неc,rbindиcbind, как в R. -
В Julia диапазон наподобие
a:bявляется не краткой формой записи вектора, как в R, а специальным объектомAbstractRange, применяемым для итерации. Преобразовать диапазон в вектор можно так:collect(a:b). -
Оператор
:имеет разный приоритет в R и Julia. В частности, в Julia арифметические операторы имеют приоритет над оператором:, в то время как в R верно обратное. Например, выражение1:n-1в Julia эквивалентно1:(n-1)в R. -
Функции
maxиminв Julia эквивалентны соответственноpmaxиpminв R, но аргументы должны иметь одинаковую размерность. Хотя функцииmaximumиminimumявляются аналогамиmaxиminв R, есть важные различия. -
Функции
sum,prod,maximumиminimumв Julia отличаются от своих аналогов в R. Все они принимают необязательный именованный аргументdims, означающий измерения, с которыми производится операция. Например, возьмем одинаковые матрицыA = [1 2; 3 4]в Julia иB <- rbind(c(1,2),c(3,4))в R. Тогдаsum(A)дает тот же результат, что иsum(B), ноsum(A, dims=1)— это строковый вектор, содержащий суммы по каждому столбцу, аsum(A, dims=2)— столбчатый вектор, содержащий суммы по каждой строке. В отличие от этого, в R в данных целях предоставляются отдельные функцииcolSums(B)иrowSums(B). Если именованный аргументdims— это вектор, то он определяет все измерения, по которым производится суммирование, с сохранением измерений суммируемого массива, напримерsum(A, dims=(1,2)) == hcat(10). Следует заметить, что второй аргумент не проверяется на наличие ошибок. -
В Julia есть ряд функций, которые могут изменять свои аргументы. Например, есть пара функций
sortиsort!. -
В R для обеспечения производительности требуется векторизация. В Julia ситуация почти обратная: зачастую быстрее выполняется код, в котором применяются невекторизированные циклы.
-
В Julia вычисления производятся немедленно; «ленивые» вычисления в стиле R не поддерживаются. Для большинства пользователей это означает редкое применение выражений без кавычек или имен столбцов.
-
В Julia не поддерживается тип
NULL. Ближайший аналог — константаnothing, но она действует как скалярное значение, а не список. Вместоis.null(x)используйтеx === nothing. -
В Julia отсутствующие значения представлены объектом
missing, а неNA. Вместоis.na(x)используйтеismissing(x)(илиismissing.(x)для поэлементных операций с векторами). Вместоna.rm=TRUEобычно применяется функцияskipmissing(хотя в некоторых особых случаях функции принимают аргументskipmissing). -
В Julia нет аналога функции R
assignилиget. -
В Julia оператор
returnне требует круглых скобок. -
В R идиоматическим способом удаления ненужных значений является использование логического индексирования, как в выражении
x[x>3]или в оператореx = x[x>3]для измененияxна месте. В Julia с этой целью предоставляются функции более высокого порядкаfilterиfilter!, которые позволяют использовать выраженияfilter(z->z>3, x)иfilter!(z->z>3, x)в качестве альтернативы дляx[x.>3]иx = x[x.>3]. Функцияfilter!позволяет реже прибегать к временным массивам.
Примечательные отличия от Python
-
Блоки
for,if,whileи т. д. в Julia заканчиваются ключевым словомend. Уровень отступа не имеет значения, как в Python. В отличие от Python, в Julia нет ключевого словаpass. -
В Julia строки обозначаются двойными кавычками, например
"text"(многострочный текст заключается в три пары двойных кавычек), в то время как в Python они могут обозначаться либо одинарными ( ), либо двойными кавычками ("text"). В Julia одинарные кавычки используются для символов ( ). -
Для конкатенации строк в Julia служит оператор
*, а не+, как в Python. Аналогичным образом, для повтора строк применяется^, а не*. Неявная конкатенация строковых литералов, возможная в Python (например, ), в Julia не поддерживается. -
Списки Python — гибкие, но медленные — соответствуют в Julia типу
Vector{Any}или в более общем случае типуVector{T}, гдеT— некоторый неконкретный тип элементов. «Быстрые» массивы, такие как массивы NumPy, элементы которых хранятся на месте (то есть типdtype—np.float64,[('f1', np.uint64), ('f2', np.int32)]и т. д.), могут быть представлены массивамиArray{T}, гдеT— конкретный, неизменяемый тип элементов. Сюда входят как встроенные типы, напримерFloat64,Int32иInt64, так и более сложные типы, напримерTuple{UInt64,Float64}, а также многие пользовательские типы. -
В Julia индексирование массивов, строк и т. д. начинается с единицы, а не с нуля.
-
При индексировании срезов в Julia последний элемент включается, в отличие от Python.
a[2:3]в Julia равносильноa[1:3]в Python. -
В отличие от Python, в Julia допустимы массивы AbstractArray с произвольными индексами. Отрицательные индексы, имеющие особый смысл в Python, например
a[-1]иa[-2], должны записываться в Julia в видеa[end]иa[end-1]. -
Для индексирования до последнего элемента в Julia требуется ключевое слово
end.x[1:]в Python эквивалентноx[2:end]в Julia. -
В Julia символ
:перед объектом создаетSymbolили заключает выражение в кавычки, поэтомуx[:5]равносильноx[5]. Чтобы получить первыеnэлементов массива, используйте индексирование диапазонов. -
В Julia индексирование диапазона имеет формат
x[start:step:stop], а в Python — форматx[start:(stop+1):step]. Поэтомуx[0:10:2]в Python эквивалентноx[1:2:10]в Julia. Аналогичным образом, выражениеx[::-1], означающее в Python перевернутый массив, эквивалентноx[end:-1:1]в Julia. -
В Julia диапазоны можно создавать отдельно в виде
start:step:stop, то есть с помощью того же синтаксиса, что и при индексировании массивов. Кроме того, поддерживается функцияrange. -
В Julia при индексировании матрицы с помощью массивов, например
X[[1,2], [1,3]], происходит обращение к подматрице, являющейся пересечением первой и второй строк с первым и третьим столбцами. В Python выражениеX[[1,2], [1,3]]означает обращение к вектору, содержащему значения ячеек[1,1]и[2,3]матрицы. ВыражениеX[[1,2], [1,3]]в Julia эквивалентноX[np.ix_([0,1],[0,2])]в Python. ВыражениеX[[0,1], [0,2]]в Python эквивалентноX[[CartesianIndex(1,1), CartesianIndex(2,3)]]в Julia. -
В Julia нет синтаксиса продолжения строк: если содержимое строки представляет собой полноценное выражение, то строка считается завершенной; в противном случае она продолжается. Один из способов продолжить выражение — заключить его в круглые скобки.
-
В Julia массивы развертываются по столбцам (как в Фортране), в то время как массивы NumPy по умолчанию развертываются по строкам (как в C). Для оптимальной производительности при переборе массивов порядок циклов в Julia должен быть обратным по сравнению с NumPy (см. соответствующий раздел главы «Советы по производительности»).
-
Операторы с присваиванием в Julia (например,
+=,-=и т. д.) выполняются не на месте, как в NumPy. Это означает, что выражениеA = [1, 1]; B = A; B += [3, 3]не изменяет значения вA. Вместо этого имяBпривязывается к результату правой частиB = B + 3, который представляет собой новый массив. Для выполнения операций на месте используйте выражения видаB .+= 3(см. также раздел, посвященный операторам с точкой), явные циклы илиInplaceOps.jl. -
В Julia значения по умолчанию аргументов функции вычисляются при каждом вызове метода, в то время как в Python это происходит только один раз при определении функции. Например, функция
f(x=rand()) = xвозвращает новое случайное число каждый раз, когда она вызывается без аргумента. С другой стороны, функцияg(x=[1,2]) = push!(x,3)возвращает[1,2,3]каждый раз, когда она вызывается какg(). -
В Julia именованные аргументы должны передаваться с указанием имен, в отличие от Python, где их обычно можно передавать как позиционные. Передача именованного аргумента как позиционного изменяет сигнатуру метода, что приводит к созданию исключения
MethodErrorили вызову неверного метода. -
В Julia
%— это оператор нахождения остатка, в то время как в Python — оператор нахождения остатка от целочисленного деления. -
В Julia часто используемый тип
Intсоответствует машинному целочисленному типу (Int32илиInt64), в то время как в Pythonint— это целое число произвольной длины. Это означает, что в Julia типIntможет переполняться, так что2^64 == 0. Если вам нужно большее значение, выберите другой подходящий тип, напримерInt128,BigIntили тип с плавающей запятой, такой какFloat64. -
Мнимая единица
sqrt(-1)представлена в Julia константойim, а неj, как в Python. -
В Julia оператором возведения в степень является
^, а не**, как в Python. -
Для представления пустого значения в Julia служит константа
nothingтипаNothing, а в Python — объектNoneтипаNoneType. -
В Julia при применении стандартного оператора к матричному типу выполняется матричная операция, в то время как в Python — поэлементная операция. Если и
A, иB— матрицы,A * Bв Julia производит умножение матриц, а не поэлементное умножение, как в Python. ВыражениеA * Bв Julia равносильноA @ Bв Python, аA * Bв Python равносильноA .* Bв Julia. -
Оператор сопряжения
'в Julia возвращает сопряжение вектора («ленивое» представление строчного вектора), в то время как оператор транспонирования.Tприменительно к вектору в Python возвращает исходный вектор (холостая операция). -
В Julia функция может иметь несколько конкретных реализаций (называемых методами), выбор из которых производится посредством множественной диспетчеризации на основе типов всех аргументов в вызове. В отличие от этого, функция в Python имеет единственную реализацию, и полиморфизм невозможен (для вызовов методов в Python применяется другой синтаксис, допускающий диспетчеризацию на стороне получателя метода).
-
В Julia нет классов. Вместо этого есть структуры (изменяемые или неизменяемые), содержащие данные, но не методы.
-
Вызов метода экземпляра класса в Python (
x = MyClass(*args); x.f(y)) соответствует вызову функции в Julia, напримерx = MyType(args...); f(x, y). В целом множественная диспетчеризация является более гибкой и эффективной, чем система классов в Python. -
У структуры Julia может быть только один абстрактный супертип, в то время как классы Python могут наследоваться от одного или нескольких суперклассов (абстрактных или конкретных).
-
В Julia логическая структура программы (пакеты и модули) не зависит от структуры файлов, в то время как в Python структура кода определяется каталогами (пакетами) и файлами (модулями).
-
В Julia совершенно нормально разбивать текст больших модулей на несколько файлов, не создавая модули для каждого файла. Код повторно собирается в едином модуле в главном файле с помощью
include. Хотя эквивалент в Python (exec) не является типичным для этого использования (он будет автоматически уничтожать предыдущие определения), программы Julia определяются как единое целое на уровнеmoduleс помощьюusingилиimport, который будет выполняться только один раз при первой необходимости (includeв Python). Внутри этих модулей отдельные файлы, составляющие модуль, загружаются с помощьюincludeи указываются в нужном порядке. -
Тернарный оператор
x > 0 ? 1 : -1в Julia соответствует в Python условному выражению1 if x > 0 else -1. -
В Julia символ
@означает макрос, а в Python — декоратор. -
Обработка исключений в Julia осуществляется с помощью конструкции
try—catch—finally, а неtry—except—finally. В отличие от Python, в Julia не рекомендуется использовать обработку исключений в рамках обычного порядка выполнения (по сравнению с Python в Julia обычный порядок выполнения быстрее, но исключения перехватываются медленнее). -
В Julia циклы выполняются быстро, поэтому писать векторизированный код для повышения производительности не нужно.
-
Будьте осторожны с неконстантными глобальными переменными в Julia, особенно в сплошных циклах. Так как в Julia (в отличие от Python) можно писать код, близкий к машинному, использование глобальных переменных может иметь серьезные последствия (см. главу Советы по производительности).
-
В Julia округление и усечение производятся явным образом. Вместо выражения Python
int(3.7)следует использоватьfloor(Int, 3.7)илиInt(floor(3.7)), причем они отличаются отround(Int, 3.7).floor(x)иround(x)сами по себе возвращают целочисленное значение того же типа, что иx, вместоInt. -
В Julia анализ текста выполняется явным образом. Выражение Python
float("3.7")должно записываться в Julia в видеparse(Float64, "3.7"). -
В Python большинство значений можно использовать как логические (например,
if "a":означает, что последующий блок выполняется, аif "":— что он не выполняется). В Julia требуется явное преобразование в типBool(например,if "a"вызовет исключение). Для проверки непустой строки в Julia нужно написать явным образомif !isempty(""). Возможно, это покажется странным, но в Pythonif "False"иbool("False")дают результатTrue(потому что"False"— это непустая строка); в Juliaparse(Bool, "false")возвращаетfalse. -
В Julia большинство блоков кода, включая циклы и конструкции
try—catch—finally, образуют новую локальную область. Обратите внимание, что включения (списковые, генераторы и т. д.) образуют новую локальную область как в Python, так и в Julia, в то время как блокиifне образуют ее в обоих языках.
Примечательные отличия от C/C++
-
Индексы массивов в Julia указываются в квадратных скобках и могут иметь несколько измерений:
A[i,j]. Это не просто синтаксический сахар для ссылки на указатель или адрес, как в C/C++. См. раздел руководства, посвященный созданию массивов. -
В Julia индексирование массивов, строк и т. д. начинается с единицы, а не с нуля.
-
Когда массив в Julia присваивается другой переменной, он не копируется. После присваивания
A = Bизменение элементовBприведет к их изменению вA. Операторы с присваиванием, например+=, выполняются не на месте. Они эквивалентныA = A + B, то есть левая часть привязывается к результату выражения в правой части. -
В Julia массивы развертываются по столбцам (как в Фортране), в то время как в C/C++ они по умолчанию развертываются по строкам. Для оптимальной производительности при переборе массивов порядок циклов в Julia должен быть обратным по сравнению с C/C++ (см. соответствующий раздел главы «Советы по производительности»).
-
В Julia присваиваемые или передаваемые в функцию значения не копируются. Если функция изменяет массив, эти изменения будут видны вызывающей стороне.
-
В Julia, в отличие от C/C++, пробелы имеют значение, поэтому их следует добавлять в программу Julia или удалять из нее с осторожностью.
-
В Julia числовые литералы без десятичного разделителя (например,
42) создают целые числа со знаком типаInt, но если литерал не помещается в машинное слово, он автоматически продвигается до типа большего размера, напримерInt64(еслиInt— это типInt32),Int128или типаBigIntпроизвольного размера. Суффиксов числовых литералов, означающих наличие знака, таких какL,LL,U,ULилиULL, нет. Десятичные литералы всегда имеют знак, а шестнадцатеричные (начинающиеся с0x, как в C/C++) не имеют, если только они не кодируют более 128 битов — в этом случае они имеют типBigInt. Кроме того, в отличие от C/C++ и Java, а также десятичных литералов в Julia, тип шестнадцатеричного литерала зависит от его длины, включая начальные символы «0s». Например,0x0и0x00имеют типUInt8,0x000и0x0000— типUInt16, литералы с 5—8 шестнадцатеричными цифрами — типUInt32, с 9—16 шестнадцатеричными цифрами — типUInt64, с 17—32 шестнадцатеричными цифрами — типUInt128, а с более чем 32 шестнадцатеричными цифрами — типBigInt. Это следует учитывать при определении шестнадцатеричных масок. Например,~0xf == 0xf0сильно отличается от~0x000f == 0xfff0. Битовые литералы длиной 64 бита (Float64) и 32 бита (Float32) выражаются как1.0и1.0f0соответственно. Литералы с плавающей запятой округляются (и не продвигаются до типаBigFloat), если их нельзя представить в точности. Литералы с плавающей запятой близки по своему поведению к C/C++. Восьмеричные литералы (с префиксом0o) и двоичные литералы (с префиксом0b) также относятся к типам без знака (или к типуBigInt, если длина более 128 бит). -
В Julia оператор деления
/возвращает число с плавающей запятой, если оба операнда целочисленного типа. Для целочисленного деления используйте функциюdivили оператор÷. -
Индексирование массива (
Array) с использованием типов с плавающей запятой обычно вызывает ошибку в Julia. Эквивалентом выражения Ca[i / 2]в Julia будетa[i ÷ 2 + 1], гдеiотносится к целочисленному типу. -
Строковые литералы могут разделяться символами
"или""“. Литералы, разделяемые с помощью `""", могут содержать символы"без экранирования (”\""`). В строковые литералы можно интерполировать значения других переменных или выражений следующим образом:$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++ (для этой цели в Julia используется⊻илиxor). -
В Julia есть два оператора сдвига вправо:
>>и>>>.>>производит арифметический сдвиг, а>>>всегда производит логический сдвиг, в отличие от 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не интерпретируются как логические значения. Написатьif (1)в Julia нельзя, так как операторifпринимает только логические значения. Вместо этого можно написатьif true,if Bool(1)илиif 1==1. -
Для обозначения конца условных блоков, таких как
if, блоков циклов, таких как иwhile/for, и функций в Julia используется ключевое словоend. Вместо однострочной записи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++ по умолчанию применяется статическая диспетчеризация. Чтобы использовать динамическую диспетчеризацию, необходимо аннотировать функцию как виртуальную. Напротив, в Julia все методы являются «виртуальными» (это следует понимать в более широком смысле, так как методы диспетчеризируются на основе всех типов аргументов, а не только
this, по правилу наиболее специфичного объявления).
Julia ⇔ C/C++: Пространства имен
-
Пространства имен (
namespace) в C/C++ примерно соответствуют модулям (module) в Julia. -
Частных глобальных переменных или полей в Julia нет. Все объекты являются общедоступными через полные пути (или при желании относительные).
-
using MyNamespace::myfun(C++) примерно соответствуетimport MyModule: myfun(Julia). -
using namespace MyNamespace(C++) примерно соответствуетusing MyModule(Julia).-
В Julia вызывающему модулю доступны только экспортированные (с помощью оператора
export) символы. -
В C++ доступны только элементы, указанные во включенных (общедоступных) файлах заголовков.
-
-
Обратите внимание: ключевые слова
importиusingв Julia также служат для загрузки модулей (см. ниже). -
Обратите внимание: ключевые слова
importиusingв Julia действуют только на уровне глобальной области (module).-
В C++
using namespace Xдействует в произвольных областях (например, в области функции).
-
Julia ⇔ C/C++: Загрузка модулей
-
Если в Julia вам нужно то, что в C/C++ называется библиотекой, то, скорее всего, это будет пакет.
-
Обратите внимание: библиотеки в C/C++ часто вмещают в себя несколько «программных модулей», в то время как в Julia пакет обычно содержит только один такой модуль.
-
Напоминание: модули (
module) в Julia — это глобальные области (необязательно «программные модули»).
-
-
Вместо скриптов сборки или создания (
make) в Julia используются «среды проектов» (иногда называемые просто «проектом» или «средой»).-
Скрипты сборки нужны только для более сложных приложений (например, требующих компиляции или скачивания исполняемых файлов C/C++).
-
Для разработки приложения или проекта в Julia можно инициализировать его корневой каталог как «среду проекта» и поместить в него относящиеся к приложению код и пакеты. Это обеспечивает хороший контроль над зависимостями проекта и воспроизводимость в будущем.
-
Доступные пакеты добавляются в «среду проекта» с помощью функции
Pkg.add()или режима Pkg в REPL. (Однако пакет при этом не загружается.) -
Список доступных пакетов (прямых зависимостей) для «среды проекта» сохраняется в ее файле
Project.toml. -
Полная информация о зависимостях для «среды проекта» генерируется автоматически и сохраняется в файле
Manifest.tomlпосредствомPkg.resolve().
-
-
Пакеты («программные модули»), доступные «среде проекта», загружаются с помощью оператора
importилиusing.-
В C/C++ для получения объявлений объектов и функций и их компоновки в библиотеках при сборке исполняемого файла используется оператор
#include <moduleheader>. -
В Julia при повторном вызове using или import существующий модуль просто вводится в область, но не загружается снова (так же, как при добавлении нестандартных директив
#pragma onceв C/C++).
-
-
Доступ к репозиториям пакетов на основе каталогов в Julia можно обеспечить путем добавления путей к репозиториям в массив
Base.LOAD_PATH.-
Перед загрузкой пакетов из репозиториев на основе каталогов с помощью оператора
importилиusingне требуется использоватьPkg.add(). Они изначально доступны в рамках проекта. -
Репозитории пакетов на основе каталогов — это самое быстрое решение для разработки локальных библиотек «программных модулей».
-
Julia ⇔ C/C++: Сборка модулей
-
В C/C++ файлы
.cи.cppкомпилируются и добавляются в библиотеку с помощью скриптов сборки или создания (make).-
В Julia операторы
import [PkgName]иusing [PkgName]загружают файл[PkgName].jlиз подкаталога[PkgName]/src/пакета. -
В свою очередь,
[PkgName].jlобычно загружает связанные файлы исходного кода посредством вызововinclude "[someotherfile].jl".
-
-
Оператор
include "./path/to/somefile.jl"в Julia очень похож на#include "./path/to/somefile.jl"в C/C++.-
Однако
include "..."в Julia не применяется для включения файлов заголовков (это не требуется). -
Не используйте
include "..."в Julia для загрузки кода из других «программных модулей» (вместо этого используйтеimportилиusing). -
include "path/to/some/module.jl"в Julia создает несколько версий одного и того же кода в разных модулях (отдельные типы и т. д. с одинаковыми именами). -
include "somefile.jl"обычно применяется для сборки нескольких файлов в одном пакете Julia («программном модуле»). Благодаря этому достаточно легко обеспечить однократное включение (include) файлов (нет путаницы с#ifdef).
-
Julia ⇔ C/C++: Интерфейс модуля
-
В C++ интерфейсы предоставляются с помощью «общедоступных» файлов
.hи.hpp, в то время как в Julia модули (module) помечают конкретные символы, предназначенные для пользователей, как общедоступные (public) или экспортированные (export).-
В Julia модули (
module) зачастую просто добавляют функциональность путем создания новых «методов» существующих функций (например,Base.push!). -
Поэтому разработчики на Julia не могут полагаться на файлы заголовков в качестве документации по интерфейсу.
-
Интерфейсы для пакетов Julia обычно описываются в строках docstring, файлах README.md, на статических веб-страницах и т. д.
-
-
Некоторые разработчики предпочитают не экспортировать (
export) все символы, необходимые для использования пакета или модуля, но они все равно должны помечать неэкспортированные символы для пользователей какpublic.-
Для доступа к этим компонентам пользователю может потребоваться указать функцию, структуру и т. д. с именем пакета или модуля (например,
MyModule.run_this_task(...)).
-
Julia ⇔ C/C++: Краткая справка
| Программная концепция | Julia | C/C++ |
|---|---|---|
неименованная область |
|
|
область функции |
|
|
глобальная область |
|
|
программный модуль |
«пакет» в Julia |
файлы |
сборка |
|
|
импорт |
|
|
библиотека модулей |
|
дополнительные файлы |
-
Диспетчер пакетов в Julia поддерживает регистрацию нескольких пакетов из одного репозитория Git.
-
Это позволяет пользователям размещать библиотеку связанных пакетов в одном репозитории.
-
Реестры Julia в первую очередь предназначены для управления версиями пакетов и их распространения.
-
С помощью пользовательских реестров пакетов можно создать тип библиотеки модулей.
-
Примечательные отличия от Common Lisp
-
В Julia массивы по умолчанию индексируются от единицы и поддерживают произвольные смещения индексов.
-
Функции и переменные находятся в одном пространстве имен (Lisp-1).
-
Есть тип
Pair, но его назначение отличается отCOMMON-LISP:CONS. В большинстве случаев (при разделении, построении кортежей и т. д.) различные итерируемые коллекции могут использоваться как взаимозаменяемые. В случае с небольшими коллекциями разнородных элементов типTupleближе всего к спискам Common Lisp. Вместо ассоциативных списков используйтеNamedTuple. Для коллекций, имеющих больший размер или содержащих разнородные элементы, следует использовать типыArrayиDict. -
Типичный рабочий процесс прототипирования в Julia также предполагает непрерывное манипулирование образом, реализуемое пакетом Revise.jl.
-
Из соображений производительности операции в Julia обычно предполагают устойчивость типов. Тогда как для Common Lisp характерно абстрагирование от базовых машинных операций, операции в Julia более тесно согласуются с ними. Например:
-
Целочисленное деление с помощью
/всегда возвращает результат с плавающей запятой, даже если он точный.-
Оператор
//всегда возвращает рациональный результат. -
Оператор
÷всегда возвращает (усеченный) целочисленный результат.
-
-
Сверхбольшие числа поддерживаются, но преобразование не выполняется автоматически; по умолчанию происходит переполнение целочисленных значений.
-
Комплексные числа поддерживаются, но для получения комплексного результата требуются комплексные входные значения.
-
Имеется несколько комплексных и рациональных типов с разными типами компонентов.
-
-
Модули (пространства имен) могут быть иерархическими. Ключевые слова
importиusingимеют двойное назначение: они загружают код и делают его доступным в пространстве имен. Импорт с помощьюimportвозможен только по имени модуля (что примерно соответствуетASDF:LOAD-OP). Имена слотов не нужно экспортировать отдельно. Глобальным переменным нельзя присваивать значения извне модуля (обойти это ограничение можно с помощью вызоваeval(mod, :(var = val))). -
Имеются макросы (начинаются с символа
@), но они не так тесно интегрированы в язык, как в Common Lisp, поэтому и применяются не так широко. Язык поддерживает гигиену макросов. Из-за различий в поверхностном синтаксисе эквивалентаCOMMON-LISP:&BODYнет. -
Все функции являются универсальными и используют множественную диспетчеризацию. Списки аргументов не обязательно должны следовать одному и тому же шаблону, что делает возможной эффективную идиоматику (см. описание ключевого слова
do). Необязательные и именованные аргументы обрабатываются по-разному. Неоднозначность методов не разрешается, как в Common Lisp Object System, из-за чего для пересечения требуется определение более конкретного метода. -
Символы не относятся в какому-либо пакету и сами по себе не содержат значений.
M.varвычисляет символvarв модулеM. -
Функциональный стиль программирования полностью поддерживается, включая замыкания, но для Julia он не характерен. При изменении захваченных переменных могут потребоваться некоторые обходные приемы для обеспечения производительности.