Spec-Zone.ru › Julia 0.6

Часто задаваемые вопросы

Сессии и REPL

Как удалить объект в памяти?

В Julia нет аналога функции MATLAB's clear; после определения имени в сессии Julia (технически, в модуле Main) оно всегда присутствует.

Если вас беспокоит использование памяти, вы всегда можете заменить объекты на такие, которые потребляют меньше памяти. Например, если A — это массив размером в гигабайт, который вам больше не нужен, вы можете освободить память с помощью A = 0. Память будет освобождена при следующем запуске сборщика мусора; вы можете принудительно запустить его с помощью gc().

Как изменить объявление типа в моей сессии?

Возможно, вы определили тип, а затем поняли, что вам нужно добавить новое поле. Если вы попытаетесь сделать это в REPL, получите ошибку:

ERROR: invalid redefinition of constant MyType

Типы в модуле Main не могут быть переопределены.

Хотя это может быть неудобно при разработке нового кода, есть отличное решение. Модули можно заменить, переопределив их, и поэтому, если вы обернёте весь новый код в модуль, вы сможете переопределить типы и константы. Вы не можете импортировать имена типов в Main и ожидать, что сможете их там переопределить, но вы можете использовать имя модуля для разрешения области видимости. Другими словами, при разработке вы можете использовать рабочий процесс примерно такой:

include("mynewcode.jl")              # this defines a module MyModule
obj1 = MyModule.ObjConstructor(a, b)
obj2 = MyModule.somefunction(obj1)
# Got an error. Change something in "mynewcode.jl"
include("mynewcode.jl")              # reload the module
obj1 = MyModule.ObjConstructor(a, b) # old objects are no longer valid, must reconstruct
obj2 = MyModule.somefunction(obj1)   # this time it worked!
obj3 = MyModule.someotherfunction(obj2, c)
...

Функции

Я передал аргумент x в функцию, изменил его внутри функции, но снаружи

переменная x по-прежнему неизменна. Почему?

Предположим, вы вызываете функцию так:

julia> x = 10
10

julia> function change_value!(y)
           y = 17
       end
change_value! (generic function with 1 method)

julia> change_value!(x)
17

julia> x # x is unchanged!
10

В Julia привязка переменной x не может быть изменена путем передачи x в качестве аргумента функции. При вызове change_value!(x) в приведенном выше примере y — это новая переменная, связанная изначально со значением x, то есть 10; затем y пересвязывается с константой 17, в то время как переменная x внешней области видимости остается неизменной.

Но вот на что следует обратить внимание: предположим, x связан с объектом типа Array (или любым другим изменяемым типом). Изнутри функции вы не можете «отвязать» x от этого массива, но вы можете изменить его содержимое. Например:

julia> x = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> function change_array!(A)
           A[1] = 5
       end
change_array! (generic function with 1 method)

julia> change_array!(x)
5

julia> x
3-element Array{Int64,1}:
 5
 2
 3

Здесь мы создали функцию change_array!(), которая присваивает 5 первому элементу переданного массива (связанного с x в месте вызова и связанного с A внутри функции). Обратите внимание, что после вызова функции x по-прежнему связан со тем же массивом, но содержимое этого массива изменилось: переменные A и x были различными привязками, ссылающимися на один и тот же изменяемый объект Array.

Можно ли использовать using или import внутри функции?

Нет, в функции нельзя использовать инструкцию using или import. Если вы хотите импортировать модуль, но использовать его символы только внутри определенной функции или набора функций, у вас есть два варианта:

  1. Используйте import:

    import Foo
    function bar(...)
        # ... refer to Foo symbols via Foo.baz ...
    end

    Это загружает модуль Foo и определяет переменную Foo, которая ссылается на модуль, но не импортирует какие-либо другие символы из модуля в текущее пространство имен. Вы обращаетесь к символам Foo с помощью их квалифицированных имён Foo.bar и т. д.

  2. Оберните функцию в модуль:

    module Bar
    export bar
    using Foo
    function bar(...)
        # ... refer to Foo.baz as simply baz ....
    end
    end
    using Bar

    Это импортирует все символы из Foo, но только внутри модуля Bar.

Что делает оператор ...?

Два использования оператора ...: сливание и разбрасывание

Многие новички в Julia считают использование оператора ... непонятным. Часть того, что делает оператор ... непонятным, заключается в том, что он означает две разные вещи в зависимости от контекста.

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

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

julia> function printargs(args...)
           @printf("%s\n", typeof(args))
           for (i, arg) in enumerate(args)
               @printf("Arg %d = %s\n", i, arg)
           end
       end
printargs (generic function with 1 method)

julia> printargs(1, 2, 3)
Tuple{Int64,Int64,Int64}
Arg 1 = 1
Arg 2 = 2
Arg 3 = 3

Если бы Julia использовала больше ASCII-символов, оператор сливания мог бы быть написан как <-... вместо ....

... разделяет один аргумент на многие разные аргументы в вызовах функций

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

julia> function threeargs(a, b, c)
           @printf("a = %s::%s\n", a, typeof(a))
           @printf("b = %s::%s\n", b, typeof(b))
           @printf("c = %s::%s\n", c, typeof(c))
       end
threeargs (generic function with 1 method)

julia> vec = [1, 2, 3]
3-element Array{Int64,1}:
 1
 2
 3

julia> threeargs(vec...)
a = 1::Int64
b = 2::Int64
c = 3::Int64

Если бы Julia использовала больше ASCII-символов, оператор разбрасывания мог бы быть написан как ...-> вместо ....

Типы, объявления типов и конструкторы

Что означает «устойчивость типа»?

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

julia> function unstable(flag::Bool)
           if flag
               return 1
           else
               return 1.0
           end
       end
unstable (generic function with 1 method)

Он возвращает либо Int, либо Float64 в зависимости от значения его аргумента. Поскольку Julia не может предсказать тип возвращаемого значения этой функции во время компиляции, любая вычисление, использующая её, должна учитывать возможность обоих типов, что затрудняет генерацию быстрого машинного кода.

Почему Julia выдаёт DomainError для некоторых, казалось бы, разумных операций?

Некоторые операции математически осмысленны, но приводят к ошибкам:

julia> sqrt(-2.0)
ERROR: DomainError:
sqrt will only return a complex result if called with a complex argument. Try sqrt(complex(x)).
Stacktrace:
 [1] sqrt(::Float64) at ./math.jl:425

julia> 2^-5
ERROR: DomainError:
Cannot raise an integer x to a negative power -n.
Make x a float by adding a zero decimal (e.g. 2.0^-n instead of 2^-n), or write 1/x^n, float(x)^-n, or (x//1)^-n.
Stacktrace:
 [1] power_by_squaring(::Int64, ::Int64) at ./intfuncs.jl:173
 [2] literal_pow(::Base.#^, ::Int64, ::Type{Val{-5}}) at ./intfuncs.jl:208

Это поведение является неудобным следствием требования устойчивости типа. В случае с sqrt() большинство пользователей хотят, чтобы sqrt(2.0) давало вещественное число и были бы недовольны, если бы оно возвращало комплексное число 1.4142135623730951 + 0.0im. Можно было бы написать функцию sqrt() для переключения на комплексное значение только при передаче отрицательного числа (что делает sqrt() в некоторых других языках), но тогда результат не был бы устойчивым к типу, и функция sqrt() имела бы низкую производительность.

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

julia> sqrt(-2.0+0im)
0.0 + 1.4142135623730951im

julia> 2.0^-5
0.03125

Почему Julia использует машинные целые числа?

Julia использует машинную арифметику для целочисленных вычислений. Это означает, что диапазон Int значений ограничен и циклически повторяется с обоих концов, поэтому при сложении, вычитании и умножении целых чисел может произойти переполнение или недополнение, что приводит к некоторым результатам, которые могут быть сначала неудивительными:

julia> typemax(Int)
9223372036854775807

julia> ans+1
-9223372036854775808

julia> -ans
-9223372036854775808

julia> 2*ans
0

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

Альтернативой может быть проверка каждого целочисленного оператора на переполнение и повышение результатов до более крупных целочисленных типов, таких как Int128 или BigInt в случае переполнения. К сожалению, это вносит значительные накладные расходы при каждой целочисленной операции (например, при инкременте счётчика цикла) — это требует вывода кода для выполнения проверок переполнения во время выполнения после инструкций арифметики и переходов для обработки возможных переполнений. Ещё хуже то, что это сделает неустойчивыми к типу все вычисления, включающие целые числа. Как мы упоминали выше, устойчивость типа имеет решающее значение для эффективной генерации эффективного кода. Если вы не можете рассчитывать на то, что результаты целочисленных операций являются целыми числами, невозможно сгенерировать быстрый и простой код так, как это делают компиляторы C и Fortran.

Вариация этого подхода, которая избегает видимости неустойчивости типа, заключается в объединении типов Int и BigInt в один гибридный целочисленный тип, который внутренне изменяет представление, когда результат больше не помещается в размер машинного целого числа. Хотя это поверхностно предотвращает неустойчивость типа на уровне кода Julia, оно просто сдвигает проблему, перекладывая все те же трудности на C-код, реализующий этот гибридный целочисленный тип. Этот подход может работать и даже может быть довольно быстрым во многих случаях, но имеет несколько недостатков. Одна проблема заключается в том, что внутреннее представление целых чисел и массивов целых чисел больше не соответствует естественному представлению, используемому языками C, Fortran и другими языками с машинными целыми числами. Таким образом, для взаимодействия с этими языками нам в конечном итоге всё равно придется ввести типы машинных целых чисел. Любое неограниченное представление целых чисел не может иметь фиксированного количества битов и, следовательно, не может храниться в массиве с фиксированными слотами — большие целочисленные значения всегда потребуют отдельного выделения памяти на куче. И, конечно, независимо от того, насколько хитрым является реализация гибридного целочисленного типа, всегда существуют ловушки производительности — ситуации, когда производительность неожиданно падает. Сложная реализация, отсутствие взаимодействия с C и Fortran, невозможность представления массивов целых чисел без дополнительного выделения памяти на куче и непредсказуемое поведение производительности делают даже самые умные реализации гибридных целочисленных типов плохим выбором для высокопроизводительных числовых вычислений.

END_OF_DOCUMENT_MARKER

Альтернативой использованию гибридных целых чисел или продвижению к BigInts является использование насыщающей арифметики целых чисел, где добавление к наибольшему целому значению оставляет его неизменным, и то же самое относится к вычитанию из наименьшего целого значения. Именно это делает Matlab™:

>> int64(9223372036854775807)

ans =

  9223372036854775807

>> int64(9223372036854775807) + 1

ans =

  9223372036854775807

>> int64(-9223372036854775808)

ans =

 -9223372036854775808

>> int64(-9223372036854775808) - 1

ans =

 -9223372036854775808

На первый взгляд, это кажется достаточно разумным, поскольку 9223372036854775807 намного ближе к 9223372036854775808, чем -9223372036854775808, и целые числа по-прежнему представлены фиксированным размером естественным образом, совместимым с C и Fortran. Однако насыщающая арифметика целых чисел глубоко проблематична. Первая и самая очевидная проблема заключается в том, что это не тот способ работы машинной арифметики целых чисел, поэтому для реализации насыщающих операций требуется вычисление инструкций после каждой машинной операции с целыми числами для проверки переполнения или недополнения и замены результата на typemin(Int) или typemax(Int) соответственно. Само по себе это расширяет каждую операцию с целыми числами с одной быстрой инструкции до полудюжины инструкций, вероятно, включая ветвления. Ой. Но ситуация ухудшается – насыщающая арифметика целых чисел не ассоциативна. Рассмотрим этот вычисление в Matlab:

>> n = int64(2)^62
4611686018427387904

>> n + (n - 1)
9223372036854775807

>> (n + n) - 1
9223372036854775806

Это затрудняет написание многих основных алгоритмов с целыми числами, поскольку многие распространённые методы зависят от того, что машинная операция сложения с переполнением является ассоциативной. Рассмотрим нахождение середины между целыми значениями lo и hi в Julia с помощью выражения (lo + hi) >>> 1:

julia> n = 2^62
4611686018427387904

julia> (n + 2n) >>> 1
6917529027641081856

Видите? Без проблем. Это правильная середина между 2^62 и 2^63, несмотря на то, что n + 2n равно -4611686018427387904. Теперь попробуйте это в Matlab:

>> (n + 2*n)/2

ans =

  4611686018427387904

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

Отсутствие ассоциативности не только неудобно для программистов, которые не могут полагаться на неё для таких техник, но и делает невозможным практически всё, что компиляторы хотели бы сделать для оптимизации арифметики целых чисел. Например, поскольку целые числа Julia используют обычную машинную арифметику целых чисел, LLVM свободно агрессивно оптимизирует простые маленькие функции, такие как f(k) = 5k-1. Машинный код этой функции – это просто:

julia> code_native(f, Tuple{Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 1
  leaq  -1(%rdi,%rdi,4), %rax
  popq  %rbp
  retq
  nopl  (%rax,%rax)

Фактическая часть функции – это единственная leaq инструкция, которая вычисляет целочисленное умножение и сложение сразу. Это ещё более выгодно, когда f встраивается в другую функцию:

julia> function g(k, n)
           for i = 1:n
               k = f(k)
           end
           return k
       end
g (generic function with 1 methods)

julia> code_native(g, Tuple{Int,Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 2
  testq %rsi, %rsi
  jle L26
  nopl  (%rax)
Source line: 3
L16:
  leaq  -1(%rdi,%rdi,4), %rdi
Source line: 2
  decq  %rsi
  jne L16
Source line: 5
L26:
  movq  %rdi, %rax
  popq  %rbp
  retq
  nop

Поскольку вызов f встраивается, тело цикла сводится к одной инструкции leaq. Далее, рассмотрим, что происходит, если мы зафиксируем количество итераций цикла:

julia> function g(k)
           for i = 1:10
               k = f(k)
           end
           return k
       end
g (generic function with 2 methods)

julia> code_native(g,(Int,))
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 3
  imulq $9765625, %rdi, %rax    # imm = 0x9502F9
  addq  $-2441406, %rax         # imm = 0xFFDABF42
Source line: 5
  popq  %rbp
  retq
  nopw  %cs:(%rax,%rax)

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

Наиболее разумной альтернативой тому, чтобы арифметика целых чисел молча переполнялась, является использование проверочной арифметики повсюду, поднимая ошибки при переполнении при сложении, вычитании и умножении, что даёт значения, не соответствующие действительности. В этой записи блога Дан Луу анализирует это и обнаруживает, что вместо тривиальных затрат, которые должен теоретически иметь этот подход, он в конечном итоге имеет значительные затраты из-за того, что компиляторы (LLVM и GCC) не оптимизируют добавление проверок переполнения. Если это улучшится в будущем, мы могли бы рассмотреть возможность по умолчанию использовать проверочную арифметику целых чисел в Julia, но пока нам приходится смириться с возможностью переполнения.

Какие возможные причины возникновения UndefVarError во время удалённого выполнения?

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

julia> module Foo
           foo() = remotecall_fetch(x->x, 2, "Hello")
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: Foo not defined
[...]

Замыкание x->x содержит ссылку на Foo, и так как Foo недоступно на узле 2, возникает UndefVarError.

Глобальные переменные в модулях, отличных от Main, не сериализуются по значению на удалённый узел. Отправляется только ссылка. Функции, которые создают глобальные связи (кроме Main), могут вызвать UndefVarError позже.

julia> @everywhere module Foo
           function foo()
               global gvar = "Hello"
               remotecall_fetch(()->gvar, 2)
           end
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: gvar not defined
[...]

В приведённом выше примере @everywhere module Foo определил Foo на всех узлах. Однако вызов Foo.foo() создал новую глобальную связь gvar на локальном узле, но она не была найдена на узле 2, что привело к ошибке UndefVarError.

Обратите внимание, что это не относится к глобальным переменным, созданным в модуле Main. Глобальные переменные в модуле Main сериализуются, а новые связи, созданные в Main на удалённом узле.

julia> gvar_self = "Node1"
"Node1"

julia> remotecall_fetch(()->gvar_self, 2)
"Node1"

julia> remotecall_fetch(whos, 2)
	From worker 2:	                          Base  41762 KB     Module
	From worker 2:	                          Core  27337 KB     Module
	From worker 2:	                           Foo   2477 bytes  Module
	From worker 2:	                          Main  46191 KB     Module
	From worker 2:	                     gvar_self     13 bytes  String

Это не относится к объявлениям function или type. Однако анонимные функции, связанные с глобальными переменными, сериализуются, как показано ниже.

julia> bar() = 1
bar (generic function with 1 method)

julia> remotecall_fetch(bar, 2)
ERROR: On worker 2:
UndefVarError: #bar not defined
[...]

julia> anon_bar  = ()->1
(::#21) (generic function with 1 method)

julia> remotecall_fetch(anon_bar, 2)
1

Пакеты и модули

В чём разница между "using" и "importall"?

Существует только одно различие, и на первый взгляд (с точки зрения синтаксиса) оно может показаться очень незначительным. Разница между using и importall заключается в том, что с using вам нужно указать function Foo.bar(.., чтобы расширить функцию bar модуля Foo новым методом, но с importall или import Foo.bar, вам нужно указать только function bar(..., и это автоматически расширяет функцию bar модуля Foo.

Если вы используете importall, то function Foo.bar(... и function bar(... становятся эквивалентными. Если вы используете using, они разные.

Причина, по которой это достаточно важно, чтобы у него был отдельный синтаксис, заключается в том, что вы не хотите случайно расширить функцию, о существовании которой вы не знали, потому что это легко может привести к ошибке. Это наиболее вероятно произойдёт с методом, который принимает общий тип, например, строку или целое число, потому что и вы, и другой модуль могут определить метод для обработки такого общего типа. Если вы используете importall, вы замените реализацию bar(s::AbstractString) другого модуля вашей новой реализацией, которая легко может сделать что-то совершенно другое (и сломать все/многие будущие использования других функций в модуле Foo, которые зависят от вызова bar).

Ничто и пропущенные значения

Как работает "null" или "ничто" в Julia?

В отличие от многих языков (например, C и Java), Julia не имеет значения "null". Когда ссылка (переменная, поле объекта или элемент массива) не инициализирована, обращение к ней немедленно вызовет ошибку. Эту ситуацию можно обнаружить с помощью функции isdefined.

Некоторые функции используются только для своих побочных эффектов и не нуждаются в возвращении значения. В этих случаях принято возвращать значение nothing, которое представляет собой просто одиночный объект типа Void. Это обычный тип без полей; в нём нет ничего особенного, кроме этой конвенции и того, что REPL не печатает ничего для него. Некоторые языковые конструкции, которые в противном случае не имели бы значения, также возвращают nothing, например, if false; end.

Для ситуаций, когда значение существует только иногда (например, отсутствуют статистические данные), лучше всего использовать тип Nullable{T}, который позволяет указать тип пропущенного значения.

Пустой кортеж (()) – это ещё одна форма ничто. Но его не следует рассматривать как ничто, а как кортеж из нулевых значений.

В коде, написанном для Julia до версии 0.4, вы иногда можете увидеть None, что совсем другое. Это пустой (или "нижний") тип, тип без значений и без подтипов (кроме самого себя). Сейчас он записан как Union{} (пустой тип объединения). Вам, как правило, не нужно будет использовать этот тип.

Память

Почему x += y выделяет память, когда x и y являются массивами?

В Julia x += y заменяется во время разбора на x = x + y. Для массивов это означает, что вместо хранения результата в том же месте памяти, что и x, выделяется новый массив для хранения результата.

Хотя это поведение может удивить некоторых, этот выбор сделан сознательно. Основная причина – наличие неизменяемых объектов в Julia, которые не могут изменить своё значение после создания. Действительно, число – это неизменяемый объект; операторы x = 5; x += 1 не изменяют значение 5, они изменяют значение, привязанное к x. Для неизменяемого объекта единственный способ изменить значение – переназначить его.

Чтобы ещё более подробно прояснить ситуацию, рассмотрим следующую функцию:

function power_by_squaring(x, n::Int)
    ispow2(n) || error("This implementation only works for powers of 2")
    while n >= 2
        x *= x
        n >>= 1
    end
    x
end

После вызова, такого как x = 5; y = power_by_squaring(x, 4), вы получите ожидаемый результат: x == 5 && y == 625. Однако теперь предположим, что *=, когда используется с матрицами, вместо этого изменяет левую часть. Возникнут две проблемы:

  • Для общих квадратных матриц A = A*B не может быть реализовано без временного хранилища: A[1,1] вычисляется и хранится в левой части, прежде чем вы закончите его использовать в правой части.

  • Предположим, вы были готовы выделить временное хранилище для вычислений (что исключило бы большую часть смысла, от того, чтобы *= работало на месте); если бы вы воспользовались изменяемостью x, эта функция работала бы по-разному для изменяемых и неизменяемых входных данных. В частности, для неизменяемого x, после вызова вы бы имели (вообще) y != x, но для изменяемого x вы бы имели y == x.

Поскольку поддержка обобщённого программирования считается более важной, чем потенциальные оптимизации производительности, которые могут быть достигнуты другими способами (например, использование явных циклов), операторы, такие как += и *=, работают путём перепривязки новых значений.

Асинхронное ввод-вывод и одновременные синхронные записи

Почему одновременные записи в один и тот же поток приводят к перемешанному выводу?

Хотя API потоковой ввода-вывода синхронно, его реализация полностью асинхронна.

Рассмотрим печатный вывод из следующего:

julia> @sync for i in 1:3
           @async write(STDOUT, string(i), " Foo ", " Bar ")
       end
123 Foo  Foo  Foo  Bar  Bar  Bar

Это происходит потому, что, хотя вызов write синхронный, запись каждого аргумента уступает другим задачам, ожидая завершения этой части ввода-вывода.

print и println «блокируют» поток во время вызова. В результате изменение write на println в приведенном выше примере приводит к:

julia> @sync for i in 1:3
           @async println(STDOUT, string(i), " Foo ", " Bar ")
       end
1 Foo  Bar
2 Foo  Bar
3 Foo  Bar

Вы можете заблокировать свои записи с помощью ReentrantLock следующим образом:

julia> l = ReentrantLock()
ReentrantLock(Nullable{Task}(), Condition(Any[]), 0)

julia> @sync for i in 1:3
           @async begin
               lock(l)
               try
                   write(STDOUT, string(i), " Foo ", " Bar ")
               finally
                   unlock(l)
               end
           end
       end
1 Foo  Bar 2 Foo  Bar 3 Foo  Bar

Выпуски Julia

Какой вариант Julia (выпуск, бета-версия или ночная сборка) мне следует использовать?

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

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

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

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

Ссылки на каждый из этих типов загрузок можно найти на странице загрузки по адресу https://julialang.org/downloads/. Обратите внимание, что не все версии Julia доступны для всех платформ.

Когда устаревшие функции удаляются?

Устаревшие функции удаляются после последующего выпуска. Например, функции, помеченные как устаревшие в выпуске 0.1, больше не будут доступны, начиная с выпуска 0.2.

© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.6/manual/faq/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API