Джулия - довольно новый язык. Однако, несмотря на то, что язык молодой, он имеет множество выдающихся функций и мощных библиотек. Он очень быстрый по сравнению с Python и сохраняет ту же простоту написания кода. К концу этого урока вы получите общее представление о Julia и начнете писать свои собственные коды на этом языке.

Как установить Юлию

Если вы используете GNU/Linux в качестве операционной системы, вы можете проверить, доступна ли Julia в репозитории вашего дистрибутива, или вы можете просто загрузить и установить Julia с ее веб-сайта: https://julialang.org/downloads/< br /> Также, если у вас MacOS, то можно установить Юлию с Доморощенным.

brew install julia

Я предлагаю установить последнюю версию, а не версию LTS (длительная поддержка). На момент написания этой статьи последней версией Джулии была v1.8.5, которую я также использую. Если у вас уже установлена ​​Julia, вы можете узнать версию, набрав julia --version в командной строке. Некоторые функции могут быть недоступны в более ранних версиях. Я расскажу вам о них позже.

Запуск Юлии в первый раз

После установки Julia вы можете запустить его, набрав julia в командной строке. Или, если вы используете Windows, у вас также должно быть приложение Julia в меню «Пуск», и вы можете запускать его оттуда. Запуск Julia должен привести к просмотру REPL Джулии.

Что такое РЕПЛ? Если вы ранее работали с Python, Clojure, Lisp или многими другими языками программирования, вы должны быть немного знакомы с REPL. REPL — это сокращение от Чтение, Оценка, Печать, Повтор. В этом режиме:

  • Джулия сначала прочитает ваши инструкции
  • оценить это
  • распечатать результат оценки
  • цикл для получения следующей инструкции

REPL — это мощный инструмент для изучения самой Джулии и экспериментов с пакетами и чужим кодом. Позже мы увидим больше вариантов использования REPL Джулии, а пока давайте начнем наше обучение с написания нашего первого и самого важного кода Джулии; Привет, мир

julia> print("Hello World")
Hello World

Если ввести это в REPL Джулии и нажать Enter, Hello World отобразится, как и ожидалось. В первой строке кода julia> указывает, что REPL Джулии управляет нашей командной строкой. Я намеренно оставил julia> во всех фрагментах кода этого документа, чтобы вы понимали, какие команды/коды вам нужно запускать внутри REPL Джулии. Вторая строка после этого — вывод.

Переменные

Переменные в Julia имеют динамический тип. Это означает, что вы можете определить переменную без указания типа и хранить в ней значения разных типов. В приведенном ниже фрагменте кода сначала мы сохраняем число в переменной a, затем проверяем тип a с помощью функции typeof, затем сохраняем логическое значение в той же переменной и снова проверяем тип.

julia> a = 42
42

julia> typeof(a)
Int64

julia> a = true
true

julia> typeof(a)
Bool

Мы также можем объявить тип наших переменных. В приведенном ниже коде мы определили b как и Int64, эта переменная больше не может хранить другие типы.

julia> b::Int64 = 1337
1337

Что произойдет, если мы попытаемся присвоить b другой тип? Давайте назначим логическое значение b.

julia> b = true
true

julia> typeof(b)
Int64

julia> b
1

После присвоения true b ошибки нет, но если мы проверим тип b, он по-прежнему будет Int64. Наконец, когда мы проверяем значение, хранящееся в b, мы понимаем, что Джулия преобразовала наше исходное логическое значение, которое мы хотели присвоить b, в Int64. Важно отметить, что это не может быть сделано для всех типов данных. Например, давайте попробуем присвоить строку b и посмотрим, что произойдет.

julia> b = "64"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
Closest candidates are:
  convert(::Type{T}, ::Ptr) where T<:Integer at pointer.jl:23
  convert(::Type{T}, ::T) where T<:Number at number.jl:6
  convert(::Type{T}, ::Number) where T<:Number at number.jl:7
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[12]:1

Как видите, мы получаем ошибку о том, что невозможно преобразовать объект типа String в объект типа Int64. Также, должен указать, что в версии 1.7 и ранее объявление типа глобальных непостоянных переменных в Джулии было невозможно. Это одна из причин, по которой ранее упоминалось об установке последней версии. Если вам интересно узнать больше о типах Джулии, ознакомьтесь с документацией: https://docs.julialang.org/en/v1/manual/types/

Еще одна интересная особенность Джулии заключается в том, что в ней гораздо больше гибкости в именовании переменных по сравнению с другими языками программирования. Имена ваших переменных могут быть любыми символами в кодировке UTF-8. Это приведет к легко читаемому коду, поскольку вы можете использовать математические символы в качестве имен переменных. Самый простой способ ввести эти символы — ввести имя символа LaTeX с обратной косой чертой и нажать клавишу Tab. Например, если я наберу \sum в REPL Джулии и нажму Tab, у меня будет символ суммирования, и я смогу присвоить ему значение.

julia> ∑ = 10
10

Или даже добавлять нижние или верхние индексы к имени переменной.

julia> a³ = 8
8

Имейте в виду, что имя переменной бессмысленно для Джулии. Так что неважно, как называется ваша переменная, Джулия будет относиться к ним одинаково. В приведенном выше случае кажется, что мы определили a в степени 3 и присвоили ему значение 8. Это не значит, что само a тоже определено и равно 2. Если вам интересно узнать больше о переменных Джулии, вот ссылка на документацию: https://docs.julialang.org/en/v1 /ручной/переменные/

Если вы когда-нибудь видели что-то интересное в чужом коде Julia и задавались вопросом, как можно напечатать то же самое, есть простое решение. В режиме REPL Джулии, где вы видите знак julia> в начале предложения, нажмите на знак вопроса (?) на клавиатуре. Теперь вы находитесь в режиме помощи. Вы можете ввести интересующее вас слово, чтобы узнать, как его напечатать, и Юля подскажет.

help?> α̂⁽²⁾
"α̂⁽²⁾" can be typed by \alpha<tab>\hat<tab>\^(2)<tab>

search:

Couldn't find α̂⁽²⁾
Perhaps you meant Base
  No documentation found.

  Binding α̂⁽²⁾ does not exist.

В приведенном выше коде мне было интересно узнать, как я могу ввести α̂⁽²⁾, и, как вы можете видеть, ответ заключается в том, что его можно ввести следующим образом:

"α̂⁽²⁾" can be typed by \alpha<tab>\hat<tab>\^(2)<tab>

После упоминания того, как этот символ может быть набран, мы видим, что также появляется сообщение о том, что этот символ не может быть найден. Это потому, что в режиме справки мы также можем вводить известные переменные, имена функций и т. д., чтобы узнать о них больше. Наш символ никогда не определялся нигде в языке или нами в нашем коде. Теперь, если мы вернемся в режим REPL Джулии, нажав обратную косую черту и определив α̂⁽²⁾, а затем вернемся в режим справки, снова нажав знак вопроса и запросив тот же символ, кроме показа, как набирать наш символ, он также предоставит больше информации о символе.

julia> α̂⁽²⁾ = 8
8

help?> α̂⁽²⁾
"α̂⁽²⁾" can be typed by \alpha<tab>\hat<tab>\^(2)<tab>

search: α̂⁽²⁾

  No documentation found.

  α̂⁽²⁾ is of type Int64.

  Summary
  ≡≡≡≡≡≡≡≡≡

  primitive type Int64

  Supertype Hierarchy
  ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡

  Int64 <: Signed <: Integer <: Real <: Number <: Any

Теперь, когда мы научились входить в режим справки, мы можем больше узнать о функциях Юлии, просто введя их название в этом режиме. Для практики подробнее о функции print читайте в режиме справки.

Условия

Представьте, что у нас есть переменная, и мы хотим проверить, является ли она четной, это код Джулии для условия:

a = 8

if a % 2 == 0
    print("a with value $a is even")
end

Если вы посмотрите внимательно, то заметите, что на этот раз в начале строки нет julia>. Это потому, что я написал этот код в файле Джулии, а не в REPL Джулии. Для этого создайте файл с расширением jl, напишите код Julia внутри этого файла и запустите файл с помощью команды julia в командной строке:

julia my_julia_file.jl

В качестве альтернативы, если вы используете Visual Studio Code, вы можете установить Julia’s Extension и запустить код внутри редактора.

Вернемся к коду, который мы написали. Первое, что вам нужно знать, это отсутствие фигурных скобок ({}) для обозначения кода внутри условия или двоеточия (:) после условия. Нам нужно только указать конец условия с помощью ключевого слова end.

Мы можем написать более сложные условия, используя else и elseif:

x = 4
y = 5

if x > y
    println("x is bigger")
elseif x < y
    println("y is bigger")
else
    println("x and y are equal")
end

Мы можем проверять меньшее или большее равно с помощью <= и >=, как и в других языках программирования, но мы также можем использовать символы Юникода для этих двух операций ( и ). Вы можете ввести эти символы, набрав \ge или \le и нажав клавишу Tab.

x = 4
y = 5

if y ≥ x
    println("y is bigger or equal")
end

Логические операции можно выполнять с помощью && для и операции и || для или операции.

x = 4
y = 5

if x % 2 == 0 || y % 2 == 0
    println("at least one of x and y is even")
end

if x % 2 == 0 && y % 2 == 0
    println("both x and y are even")
end

if !(y % 2 == 0)
    println("x is odd")
end

Мы также можем использовать ! для логической операции not.

Имейте в виду, что в Джулии также есть & и | для логических операций, но они немного отличаются от && и ||. Обычно мы используем && и || для наших логических операций, потому что это остановит вычисление логического выражения как можно скорее. Представьте, что у нас есть набор логических переменных, и у нас есть и операция между всеми ними:

println(b1 && b2 && b3 && b4 && b5)

Если b1 равно false, нет смысла проверять остальные, так как все выражение в любом случае окажется ложным. То же самое касается или операции, но для истинных значений. Как только мы найдем значение true, независимо от того, каково значение остальных, все выражение наверняка будет true. Но если мы используем & и |, Джулия не будет замыкать вычисление выражения, как только найдет ответ, и все равно будет вычислять их все. Это полезно, когда мы хотим оценить все выражение, а не только конечный результат.

Петли

В Джулии есть цикл for и цикл while. while будет зацикливаться до тех пор, пока его состояние равно true:

n = 1

while n < 6
    println(n)
    global n += 1
end

В приведенном выше коде мы считаем от 1 до 5. Как только n достигает 6, условие n < 6 будет оцениваться как false, и цикл останавливается. Для увеличения n на 1 внутри while мы должны указать, что нам нужен доступ к global переменной n, так как n было определено вне области действия while и иначе недоступно.

Циклы for дадут нам возможность перебирать итерируемый объект. Для выполнения такого же подсчета с for мы можем исправить следующий код:

for i in 1:5
    println(i)
end

Здесь 1:5 — это объект диапазона, представляющий последовательность чисел от 1 до 5, и мы повторяем этот объект с переменной i. Мы также можем определить шаги в нашей последовательности чисел, добавив третье число в середине. Для подсчета четных чисел от 0 до 20 мы можем написать код ниже. 0:2:20 — это объект диапазона, представляющий последовательность чисел от 0 до 20 с шагом, равным 2.

for i in 0:2:20
    println(i)
end

На примерах объекта диапазона вы уже должны были заметить, что последовательность чисел включает как начальное, так и конечное число ( 1:5 включает в себя и 1, и 5).

Имейте в виду, что если мы никогда не определяли переменную i в нашем коде, вне цикла for она не будет доступна и определена. И если мы определили его раньше, вне цикла for его значение будет тем же значением, которое мы присвоили ему вне цикла.

i = -1

for i in 1:5
    println(i)
end

println("This is the value of i outside the loop: $i")

Что вы думаете о последнем println, какое значение i он собирается напечатать? Запустите код и посмотрите, правильно ли вы угадали.

Мы можем управлять потоком наших циклов с помощью выражений break и continue. В то время как break полностью завершит цикл, continue пропустит остальную часть цикла и начнет следующую итерацию.

Представьте, что в последовательности чисел от 1 до 100 мы хотим найти первое число, которое делится на 2, 3 и 4.

for i in 1:100
    if i % 2 == 0 && i % 3 == 0 && i % 4 == 0
        println(i)
        break
    end
end

Как только мы находим наш номер и print его, мы break завершаем цикл, так как он больше не нужен.

Теперь представьте, что мы хотим напечатать все числа от 1 до 20, кроме тех, которые делятся на 3.

for i in 1:20
    if i % 3 == 0
        continue
    end
    println(i)
end

В условии if, если i делится на 3, мы выполним цикл continue и опустим остальные выражения, что означает, что println(i) не будет выполнено.

Массивы

В Julia у нас могут быть массивы определенного типа или с типом Any, который может хранить любой тип.

julia> a = [1, 2, 3, 4]
4-element Vector{Int64}:
 1
 2
 3
 4

julia> typeof(a)
Vector{Int64} (alias for Array{Int64, 1})

Если мы инициализируем массив определенным типом, этот массив будет иметь этот определенный тип и не будет принимать другие значения в своих элементах. В примере выше, как видите, тип Int64.

julia> a[1] = "Hello World"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int64
Closest candidates are:
  convert(::Type{T}, ::Ptr) where T<:Integer at pointer.jl:23
  convert(::Type{T}, ::T) where T<:Number at number.jl:6
  convert(::Type{T}, ::Number) where T<:Number at number.jl:7
  ...
Stacktrace:
 [1] setindex!(A::Vector{Int64}, x::String, i1::Int64)
   @ Base ./array.jl:966
 [2] top-level scope
   @ REPL[3]:1

Однако, если тип Any, он примет любое значение в своих элементах.

julia> b = [1, "two", 3.0]
3-element Vector{Any}:
 1
  "two"
 3.0

julia> typeof(b)
Vector{Any} (alias for Array{Any, 1})

julia> b[1] = true
true

Если вы хотите инициализировать массив только одним определенным типом, но хотите, чтобы массив имел тип Any, чтобы в будущем он мог принимать другие типы, вы можете указать ключевое слово Any перед инициализацией массива.

julia> c = Any[1, 2, 3]
3-element Vector{Any}:
 1
 2
 3

julia> typeof(c)
Vector{Any} (alias for Array{Any, 1})

julia> c[1] = "One"
"One"

Одна важная особенность массивов Джулии заключается в том, что индекс начинается с 1, а не с 0.

Массивы в Julia изменяемы, для добавления значения в конец массива можно использовать push!.

julia> a = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> push!(a, 4)
4-element Vector{Int64}:
 1
 2
 3
 4

Восклицательный знак в конце функции push! указывает на то, что эта функция будет иметь побочные эффекты, в нашем случае изменение содержимого одного из ее аргументов, которым является массив a. Рекомендуется следовать тому же соглашению, когда мы пишем свои собственные функции, и добавлять восклицательный знак в конце имени функции, если она собирается изменить какой-либо из своих аргументов.

Мы также можем использовать pushfirst! для добавления элемента в начало массива.

julia> a = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> pushfirst!(a, 0)
4-element Vector{Int64}:
 0
 1
 2
 3

Подробнее о массивах Джулии можно прочитать в документации: https://docs.julialang.org/en/v1/base/arrays/

Также многие функции, которые мы используем для работы с массивами, являются общими для всех коллекций в Джулии, поэтому вы можете найти их здесь: https://docs.julialang.org/en/v1/base/collections/

Для практики откройте ссылку на документацию коллекций и прочитайте о функции popat! и посмотрите, как она работает.

Функции

Мы можем определить функцию Джулии, которая суммирует две переменные и возвращает их следующим образом:

function sum_two_numbers(x, y)
    return x + y
end

println(sum_two_numbers(1, 2))

Как видите, как и при определении условий и циклов, нет необходимости в двоеточии (:) или фигурных скобках ({}). Просто ключевое слово end в конце функции. Мы также можем указать типы аргументов функции и тип, который она будет возвращать.

function sum_two_numbers(x::Int64, y::Int64)::Int64
    return x + y
end

println(sum_two_numbers(1, 2))

Мы специально определили тип аргументов, который равен Int64, и тип возвращаемого значения функции, который также равен Int64.

Если мы указываем типы аргументов функции, мы больше не можем передавать аргументы разных типов. Но тип возвращаемого значения не такой строгий, как типы аргументов. Если возможно, Julia преобразует возвращаемое значение в возвращаемый тип функции, а если не может, выдаст ошибку. Имейте в виду, что то же самое не относится к аргументам функции, и вы получите ошибку, если передадите значения неправильного типа.

function sum_two_floats(x::Float64, y::Float64)::Int64
    return x + y
end

println(sum_two_floats(1.0, 2.0))

Если вы запустите приведенный выше код, даже несмотря на то, что мы указали, что возвращаемый тип будет Int64, и мы вернем сумму двух чисел Float64, которая будет Float64, мы не получим ошибку, и Джулия преобразует результат сумма, которая составляет от 3.0 до 3 . Но если мы передадим этой функции разные числа, скажем, 1.0 и 2.5, результат суммы будет 3.5, и Джулия не сможет преобразовать это значение в Int64 и выдаст ошибку. Попробуйте и посмотрите, в чем ошибка.

В функциях, если мы не используем оператор return, последнее выражение будет возвращено автоматически. Рассмотрим следующую функцию, которая должна возвращать сумму квадратов двух переменных.

function sum_of_squared(x, y)
    x² = x * x
    y² = y * y
    x² + y²
end

println(sum_of_squared(2, 3))

Поскольку мы ничего не вернули, будет возвращено последнее выражение x² + y².

Мы также можем определить функции другим способом, это функция sum_of_squared, определенная в одной строке, которая эквивалентна той, которую мы определили ранее:

sum_of_squared(x, y) = (x * x) + (y * y)

println(sum_of_squared(2, 3))

Мы также можем пойти дальше и определить анонимную функцию. Они полезны, когда мы хотим передать функцию другой функции и не нуждаемся в этой функции снова. Например, представьте, что у нас есть список чисел, и нам нужно квадратное значение всех этих чисел. Мы можем использовать map. map примет в качестве входных данных массив и функцию и применит функцию к каждому элементу массива. В нашем случае функция должна принимать на вход число и возвращать квадрат этого числа. Мы можем написать это так.

a = [1, 2, 3, 4, 5]

function square(x)
    return x * x
end

square_a = map(square, a)

println(square_a)

Но более удобный способ написать тот же код — использовать анонимные функции. Мы можем определить анонимные функции, сначала написав их аргументы, используя стрелку (->), а затем написав значение, которое должно быть возвращено после этого.

a = [1, 2, 3, 4, 5]

square_a = map(x -> x * x, a)

println(square_a)

В приведенном выше коде x -> x * x — это наша анонимная функция. Мы также можем присвоить эту анонимную функцию переменной, чтобы иметь возможность использовать ее позже. В конечном итоге все три примера в приведенном ниже коде эквивалентны и делают одно и то же.

function first_square(x)
    x * x
end

second_square(x) = x * x

third_square = x -> x * x

println(first_square(2))
println(second_square(2))
println(third_square(2))

Чтобы узнать больше о функциях Джулии, вы можете проверить документацию Джулии: https://docs.julialang.org/en/v1/manual/functions/

Методы

Ранее мы видели, что в Julia можно указать тип аргументов функции. Это даст нам возможность определить одну и ту же функцию для разных типов аргументов или разного количества аргументов. Каждая реализация одной и той же функции называется методом. Выбор метода для вызова на основе типов аргументов и количества аргументов называется отправкой. Более подробное объяснение методов можно найти в Документации Джулии по методам.

Чтобы упростить использование многих различных реализаций одной и той же концепции, функции не нужно определять все сразу, а можно определять по частям, предоставляя определенное поведение для определенных комбинаций типов аргументов и счетчиков. Определение одного возможного поведения функции называется методом. До сих пор мы представили только примеры функций, определенных с помощью одного метода, применимого ко всем типам аргументов. Однако сигнатуры определений методов могут быть аннотированы для указания типов аргументов в дополнение к их количеству, и может быть предоставлено более одного определения метода. Когда функция применяется к определенному набору аргументов, применяется наиболее конкретный метод, применимый к этим аргументам.

В качестве примера представьте, что мы хотим напечатать тип и значение переменной. Мы можем создать несколько диспетчеров для типа значения, которое мы передаем нашей функции.

function print_type_and_value(x::Int64)
    println("The type of variable is Int64 and the value is $x")
end

function print_type_and_value(x::Float64)
    println("The type of variable is Float64 and the value is $x")
end

function print_type_and_value(x::String)
    println("The type of variable is String and the value is $x")
end

print_type_and_value(42)
print_type_and_value(42.0)
print_type_and_value("Forty Two")

Одна и та же концепция может быть применена к различному количеству аргументов для создания нескольких диспетчеров одной и той же функции.

function average(a, b)
    (a + b) / 2
end

function average(a, b, c)
    (a + b + c) / 3
end

println(average(1, 2))
println(average(1, 2, 3))

Чтобы увидеть различные методы функции, мы можем использовать функцию methods. Сначала мы можем включить наш код в REPL Джулии, а затем проверить различные методы функций, которые мы написали. Обратите внимание, что я записал свой код в файл с именем multiple_dispatch.jl, вы должны написать свое собственное имя файла.

julia> include("multiple_dispatch.jl")

julia> methods(print_type_and_value)
# 3 methods for generic function "print_type_and_value":
[1] print_type_and_value(x::Int64) in Main at /path_to/multiple_dispatch.jl:1
[2] print_type_and_value(x::Float64) in Main at /path_to/multiple_dispatch.jl:5
[3] print_type_and_value(x::String) in Main at /path_to/multiple_dispatch.jl:9

Структуры

В Julia нет классов, но структуры можно использовать для создания составных типов, подобных классам. Составные типы в Джулии определены в документации типов.

Составные типы в разных языках называются записями, структурами или объектами. Составной тип — это набор именованных полей, экземпляр которого можно рассматривать как одно значение. Во многих языках составные типы являются единственным типом, определяемым пользователем, и они также являются наиболее часто используемым определяемым пользователем типом в Julia.

Например, это struct для составного типа человека. Указание типа полей не является обязательным. После определения struct мы можем создать объект из типа Person.

struct Person
    name::String
    age::Int
    adult::Bool
end

p1 = Person("John", 20, true)
println(p1.name)
println(p1.age)
println(p1.adult)

По умолчанию в Julia структуры неизменяемы, то есть после создания объекта мы не можем изменить значение его полей. Выполнение приведенного ниже кода после определения p1 приведет к ошибке.

p1.name = "Jane"

Для создания изменяемых структур перед определением структуры следует использовать ключевое слово mutable.

mutable struct Person
    name::String
    age::Int
    adult::Bool
end

p1 = Person("John", 20, true)
println(p1.name)

p1.name = "Jane"
println(p1.name)

Мы также можем определить собственный конструктор для типа Person, добавив внутрь нашей структуры функцию с тем же именем, что и у структуры. Как мы видели ранее, можно определить одну и ту же функцию, но с разными типами аргументов или с разным количеством аргументов. Мы можем использовать это, чтобы создать более одного конструктора для типа Person.

struct Person
    name::String
    age::Int
    adult::Bool

    function Person(name::String, age::Int)
        adult = age >= 18
        new(name, age, adult)
    end

    function Person(age::Int)
        adult = age >= 18
        new("Anonymous", age, adult)
    end

end

p1 = Person("John", 20)
println(p1.name)
println(p1.age)
println(p1.adult)

p2 = Person(16)
println(p2.name)
println(p2.age)
println(p2.adult)

В приведенном выше коде тип Person имеет два конструктора. Первый конструктор получает имя и возраст и присваивает логическое поле adult в зависимости от того, превышает ли возраст 18 лет. Во втором конструкторе принимается только age, а для имени устанавливается значение Anonymous. Мы можем сказать, что у нас есть два способа создать объект из типа Person, предоставив name и age, или просто используя age.

Пакеты и среды

Есть два основных способа установки пакетов в Julia. Вы можете импортировать Pkg и использовать функцию add для установки новых пакетов, или вы можете войти в пакетный режим в REPL и установить там новые пакеты. Для первого метода это пример установки пакета Faker.

julia> using Pkg

julia> Pkg.add("Faker")
    Updating registry at `~/.julia/registries/General.toml`
   Resolving package versions...
    Updating `~/.julia/environments/v1.8/Project.toml`
  [0efc519c] + Faker v0.3.5
    Updating `~/.julia/environments/v1.8/Manifest.toml`
  [0efc519c] + Faker v0.3.5
  [69024149] + StringEncodings v0.3.6
  [ddb6d928] + YAML v0.4.8

Чтобы лучше управлять своими пакетами и средами, вы можете войти в режим пакетов в REPL Джулии, введя ]. После этого вы должны увидеть (@v1.8) pkg> в командной строке. (@v1.8) указывает на активную в данный момент среду. Пакеты будут установлены в этой среде. В этом режиме, набрав status или st, что является более короткой версией той же команды, вы можете увидеть список всех пакетов Julia, которые вы установили в среде вашей системы по умолчанию.

(@v1.8) pkg> st
Status `~/.julia/environments/v1.8/Project.toml`
  [336ed68f] CSV v0.10.9
  [a93c6f00] DataFrames v1.5.0
  [0efc519c] Faker v0.3.5
  [91a5bcdd] Plots v1.38.5

Как видите, в моей системе установлены CSV, DataFrames, Faker и Plots. Мы можем удалить пакет Faker, который мы установили ранее, с помощью команды remove или rm, а затем проверить список пакетов с помощью st, чтобы убедиться, что он был удален.

(@v1.8) pkg> rm Faker
    Updating `~/.julia/environments/v1.8/Project.toml`
  [0efc519c] - Faker v0.3.5
    Updating `~/.julia/environments/v1.8/Manifest.toml`
  [0efc519c] - Faker v0.3.5
  [69024149] - StringEncodings v0.3.6
  [ddb6d928] - YAML v0.4.8

(@v1.8) pkg> st
Status `~/.julia/environments/v1.8/Project.toml`
  [336ed68f] CSV v0.10.9
  [a93c6f00] DataFrames v1.5.0
  [91a5bcdd] Plots v1.38.5

Для переустановки пакета Faker в этом режиме можно использовать команду add.

(@v1.8) pkg> add Faker
   Resolving package versions...
    Updating `~/.julia/environments/v1.8/Project.toml`
  [0efc519c] + Faker v0.3.5
    Updating `~/.julia/environments/v1.8/Manifest.toml`
  [0efc519c] + Faker v0.3.5
  [69024149] + StringEncodings v0.3.6
  [ddb6d928] + YAML v0.4.8

(@v1.8) pkg> st
Status `~/.julia/environments/v1.8/Project.toml`
  [336ed68f] CSV v0.10.9
  [a93c6f00] DataFrames v1.5.0
  [0efc519c] Faker v0.3.5
  [91a5bcdd] Plots v1.38.5

До сих пор мы добавляли и удаляли пакеты в проект по умолчанию (окружение) нашей системы. Путь к этому проекту указан в первой строке, возвращаемой командой status. Мы также можем создавать собственные проекты с собственным окружением. Создайте новый каталог для нового проекта и запустите julia внутри этого каталога. Теперь вы можете перейти в режим пакета по ] и создать новое окружение для вашего проекта.

(@v1.8) pkg> activate .
  Activating new project at `~/src/julia/new_project`

После запуска команды activate . Джулия активирует новый проект по пути ~/src/julia/new_project, который является путем моего нового проекта. Имейте в виду, что . указывает на текущую папку, поэтому мы указываем, что новый проект будет находиться в текущей папке. Если мы запустим st, мы увидим следующий результат.

(new_project) pkg> st
Status `~/src/julia/new_project/Project.toml` (empty project)

Как видите, в нашем проекте пакета нет, так как Юля создала новую чистую среду для нашего проекта. Мы можем начать добавлять необходимые пакеты в наш проект.

(new_project) pkg> add Faker
    Updating registry at `~/.julia/registries/General.toml`
   Resolving package versions...
    Updating `~/src/julia/new_project/Project.toml`
  [0efc519c] + Faker v0.3.5
    Updating `~/src/julia/new_project/Manifest.toml`
  [0efc519c] + Faker v0.3.5
  [692b3bcd] + JLLWrappers v1.4.1
  ...

Если вы проверите папку, созданную для вашего проекта, вы должны увидеть два файла с именами Project.toml и Manifest.toml. Эти файлы будут созданы после того, как вы добавите первый пакет в новый проект. Это объяснение в Документации Джулии относительно файла Project.toml.

Файл проекта описывает проект на высоком уровне, например, зависимости пакета/проекта и ограничения совместимости перечислены в файле проекта.

И описание Manifest.toml в Документации Джулии.

Файл манифеста — это абсолютная запись состояния пакетов в среде. Он включает в себя точную информацию о (прямых и косвенных) зависимостях проекта. Имея пару Project.toml + Manifest.toml, можно создать экземпляр точно такой же среды пакета, что очень полезно для воспроизводимости.

Другими словами, Project.toml включает все пакеты, которые вы явно добавили в проект, но Manifest.toml также включает пакеты, которые были добавлены, поскольку они были зависимостями от пакетов, которые вы добавили в свой проект. Также Manifest.toml содержит дополнительную информацию о необходимых пакетах, такую ​​как URL-адреса незарегистрированных пакетов и т. д. Project.toml – это минимум, необходимый для установки зависимостей проекта, в то время как Manifest.toml также сохраняет своего рода снимок вашего проекта с более точной информацией. . Вы можете предоставить свой файл Project.toml только тогда, когда хотите поделиться своими проектами с другими, или также включить Manifest.toml. Ясно, что рекомендуется включать Manifest.toml, так как это приведет к репликации среды, более похожей на ту, которая изначально была в вашей собственной системе.

Для активации среды по умолчанию вы можете просто запустить команду activate без каких-либо других аргументов.

(new_project) pkg> activate
  Activating project at `~/.julia/environments/v1.8`

Теперь давайте посмотрим, как можно использовать чужой проект. В качестве практики я создал репозиторий GitHub для создания поддельных писем с пакетом Faker в Julia. Это ссылка на этот репозиторий: https://github.com/SMMousaviSP/FakeMailGenerator

После клонирования репозитория вы можете запустить julia в папке проекта и выполнить следующие команды в режиме пакета.

(@v1.8) pkg> activate .
  Activating project at `~/src/julia/FakeMailGenerator`

(FakeMailGenerator) pkg> instantiate

Сначала мы активируем новое окружение для проекта FakeMailGenerator командой activate .. Затем мы запускаем instantiate, который установит необходимые пакеты для этого проекта. Вот более подробное объяснение этих двух команд из Документации Джулии.

Обратите внимание, что activate сам по себе не устанавливает отсутствующие зависимости. Если у вас есть только Project.toml, Manifest.toml должен быть сгенерирован путем «разрешения» среды, а затем все отсутствующие пакеты должны быть установлены и предварительно скомпилированы. instantiate делает все это за вас.

Если у вас уже есть разрешенный Manifest.toml, вам все равно нужно убедиться, что пакеты установлены и имеют правильные версии. И снова instantiate делает это за вас.

Короче говоря, instantiate поможет вам убедиться, что среда готова к использованию. Если делать нечего, instantiate ничего не делает.

После установки зависимостей вы можете запустить проект, набрав julia в командной строке и указав путь к проекту. Это путь среды, которую вы создали для своего проекта, поэтому он всегда должен указывать на основной каталог вашего проекта.

julia --project=. generator.jl

В этом случае, поскольку я уже в папке проекта, я указываю путь к проекту ., который указывает на текущую папку.

Что дальше

До сих пор мы обсудили большинство основных вещей, которые вам нужно знать, чтобы начать писать собственный код в Julia. Ниже вы можете найти список важных вещей, которые вам нужно знать о Джулии, которые были подробно рассмотрены в этом посте.

  • В REPL Джулии вы можете перейти в режим справки, набрав ?, и в режим пакета, набрав ].
  • Переменные в Julia имеют динамический тип, но вы можете объявить тип (например, number::Int64 = 1337)
  • Такие блоки, как условия, циклы и т. д., не нуждаются в фигурных скобках или двоеточиях, достаточно ключевого слова end в конце.
  • Массивы индексируются с 1. Также объект диапазона включает как начальный, так и конечный номер (1:5 — это последовательность чисел, включающая как 1, так и 5)
  • Можно определить тип аргументов и тип возвращаемого значения функции. Если вы вернете другой тип, Julia попытается преобразовать его в возвращаемый тип, если это возможно, но то же самое не верно для аргументов.
  • В Julia можно определить несколько отправок одной и той же функции, которая называется методами. Идея состоит в том, что одну и ту же функцию можно написать для разных типов аргументов или разного количества аргументов.
  • Структуры — это составные типы в Julia, похожие на классы.
  • Одна структура может иметь более одного конструктора за счет использования нескольких диспетчеров одной и той же функции.
  • Юлия будет управлять средой проекта для нас.
  • Для создания/активации окружения можно использовать команду activate в режиме проекта с указанием пути к проекту (., если вы в данный момент находитесь в папке проекта)
  • instantiate установит пакеты уже существующего проекта, не забудьте сначала activate среду.

Запачкайте руки. Начните писать несколько программ, используя все, что вы уже изучили. Очень рекомендую прочитать Документацию Джулии. Понятно и очень хорошо написано. Также начните искать пакеты в интересующих вас областях. Нравится ли вам визуализация данных? Попробуйте Сюжеты. Если вы заинтересованы в машинном обучении, посмотрите Flux. Для веб-разработки вы можете попробовать Genie.