Nesta publicação, focaremos o principal, na minha opinião, característica distintiva da linguagem Julia - a representação de funções na forma de métodos com despacho múltiplo. Isso permite aumentar o desempenho dos cálculos sem reduzir a legibilidade do código e sem prejudicar a abstração, por um lado, e permite trabalhar com conceitos matemáticos em uma notação mais familiar, por outro. Como exemplo, a questão do uniforme (do ponto de vista das operações lineares) trabalha com polinômios na representação da lista de coeficientes e com polinômios de interpolação.
Sintaxe básica
Uma breve introdução para quem não conhece. Julia é uma linguagem semelhante a script, possui REPL (loop de leitura-avaliação-impressão, ou seja, um shell interativo). À primeira vista, parece bastante semelhante, por exemplo, ao Python ou ao MATLAB.
Operações aritméticas
A aritmética é quase a mesma de todos os lugares: +, -, *, /, ^ para exponenciação, etc.
Comparação:>, <,> =, <=, == ,! = Etc.
Tarefa: =.
Características: a divisão por 
/ sempre retorna um número fracionário; se você precisar da parte inteira da divisão de dois inteiros, precisará usar a operação 
div(m, n) ou o infixo equivalente 
m ÷ n .
Tipos
Tipos numéricos:
- Inteiros ( Int) -2,3,-42
- Inteiros não assinados ( UInt) -0x12345
- Ponto flutuante ( Float32,Float64) -1.0,3.1415,-Inf,NaN
- Rational ( Rational) -3//3,7//2
- Real ( Real) - todos os itens acima
- Complexo ( Complex) -3+4*im,2//3+2//3*im,3.0+0.0*im(imé uma unidade imaginária, apenas um número com uma parte imaginária explicitamente escrita é considerado complexo)
- Number- todos os itens acima
Cordas e caracteres:
- 'a'(- Char)
- "a"é uma string (- String)
Nota: as strings, como agora em muitos idiomas, são imutáveis.
NB: strings (assim como nomes de variáveis) suportam Unicode, incluindo emoji.
Matrizes:
- x = [1, 2, 3]- especificando uma matriz por enumeração direta de elementos
- construtores especiais: zeros(length)para uma matriz de zeros,ones(length)para uma matriz de unidades,rand(length)para uma matriz de números aleatórios, etc.
- suporte a array multidimensional
- suporte para operações de álgebra linear (adição de matrizes, multiplicação escalar, multiplicação de vetores matriciais e muito mais) na biblioteca padrão
Nota: todas as coleções são indexadas a partir de uma.
NB: porque como a linguagem é destinada a tarefas computacionais, as matrizes são um dos tipos mais importantes; você precisará retornar aos princípios de seu trabalho mais de uma vez.
Tuplas (conjunto de elementos ordenado, imutável):
- (2, 5.3, "k")é uma tupla regular
- (a = 3, b = 4)- tupla nomeada
NB: os campos de uma tupla nomeada podem ser acessados pelo nome por um período e pelo índice via []
 julia> x = (a = 5, b = 12) (a = 5, b = 12) julia> x[1] 5 julia> sqrt(xa^2 + x[2]^2) 13.0 
Dicionários:
 julia> x = Dict('a' => 5, 'b' => 12) Dict{Char,Int64} with 2 entries: 'a' => 5 'b' => 12 julia> x['c'] = 13 13 julia> x Dict{Char,Int64} with 3 entries: 'a' => 5 'c' => 13 'b' => 12 
Construções básicas da linguagem de controle
1. As variáveis são criadas automaticamente na atribuição. O tipo é opcional.
 julia> x = 7; x + 2 9 julia> x = 42.0; x * 4 168.0 
2. O bloco de salto condicional começa com a expressão 
if <condition> e termina com a palavra 
end . Você também pode ter uma 
else luz ou outras luzes:
 if x > y println("X is more than Y") elseif x == y println("X and Y are equal") else println("X is less than Y") end 
3. Existem duas construções de loop: 
while e 
for . O segundo funciona como em Python, ou seja, Repete a coleção. Um uso comum é iterativo em um intervalo de valores cuja sintaxe é 
start[:increment]:end . Ao contrário do Python, um intervalo 
inclui valores iniciais e finais, ou seja, o intervalo vazio não será 
1:1 (este é um intervalo de 1), mas 
1:0 . O final do corpo do loop é marcado com a palavra 
end .
 julia> for i in 1:3; print(i, " "); end  
4. As funções são definidas pela palavra-chave 
function , a definição da função também termina com a palavra 
end . Argumentos com valores padrão e argumentos nomeados são suportados.
 function square(x) return x * x end function cube(x) x * square(x)  
Em geral, tudo isso é bastante semelhante ao Python, exceto por pequenas diferenças na sintaxe e pelo fato de os blocos de código não serem alocados com espaços, mas ainda com palavras-chave. Em casos simples, os programas Python se traduzem em Julia quase um a um.
Mas há uma diferença significativa no fato de que em Julia você pode especificar explicitamente tipos de variáveis, o que permite compilar programas, obtendo código rápido.
A segunda diferença significativa é que o Python implementa um modelo de POO “clássico” com classes e métodos, enquanto Julia implementa um modelo de despacho múltiplo.
Anotações de tipo e expedição múltipla
Vamos ver o que é uma função interna:
 julia> sqrt sqrt (generic function with 19 methods) 
Como o REPL nos mostra, o 
sqrt é uma função genérica com 19 métodos. Que tipo de função generalizada e que tipo de métodos?
E isso significa que existem 
várias funções 
sqrt que se aplicam a diferentes tipos de argumentos e, portanto, calculam a raiz quadrada usando vários algoritmos. Você pode ver quais opções estão disponíveis, digitando
 julia> methods(sqrt) 
Pode-se observar que a função é definida para diferentes tipos de números, bem como para matrizes.
Diferente do OOP “clássico”, em que a implementação concreta do método é determinada apenas pela classe de chamada (despachada pelo primeiro argumento), em Julia a escolha de uma função é determinada pelos tipos (e número) de 
todos os seus argumentos.
Ao chamar uma função com argumentos específicos de todos os seus métodos, é selecionado um que descreva com mais precisão o conjunto específico de tipos com os quais a função é chamada e é ela que é usada.
Uma característica distintiva é que é aplicada uma abordagem chamada compilação "antecipadamente" pelos autores da linguagem. I.e. As funções são compiladas para os tipos de dados fornecidos na primeira chamada, após o que as seguintes chamadas são realizadas muito mais rapidamente. A diferença entre a primeira e a subseqüente chamada pode ser muito significativa:
 julia> @time sqrt(8)  
No caso ruim, cada chamada de função é uma verificação de tipo dos argumentos recebidos e a procura do método desejado na lista. No entanto, se você der dicas ao compilador, poderá eliminar as verificações, o que levará a um código mais rápido.
Por exemplo, considere calcular a soma
 function mysqrt(num)  
O benchmark mostra que a função 
S_typed() não é executada apenas mais rapidamente, mas também não requer alocação de memória para cada chamada, ao contrário de 
S() . O problema aqui é que o tipo do 
mysqrt() retornado do 
mysqrt() não está definido, assim como o tipo do lado direito da expressão
 sum = sum + mysqrt(sgn) 
Como resultado, o compilador não consegue nem descobrir qual será a 
sum tipo a cada iteração. Portanto, o boxe (vinculação de etiqueta de tipo) é uma variável e a memória é alocada.
Para a função 
S_typed() , o compilador sabe de antemão que 
sum é um valor complexo, portanto o código é mais otimizado (em particular, chamar 
mysqrt() pode ser efetivamente incorporado, retornando sempre o valor de retorno para 
Complex ).
Mais importante, para 
S_typed() compilador sabe que o valor de retorno é do tipo 
Complex , mas para 
S() tipo de valor de saída não é definido novamente, o que desacelerará todas as funções em que 
S() será chamado.
Você pode verificar se o compilador pensa nos tipos retornados da expressão usando a macro 
@code_warntype :
 julia> @code_warntype S(3) Body::Any  
Se uma função é chamada em algum lugar do loop para o qual 
@code_warntype não pode imprimir o tipo de retorno, ou para o qual em algum lugar do corpo mostra o recebimento de um valor do tipo 
Any , a otimização dessas chamadas provavelmente dará um aumento de desempenho muito tangível.
Tipos de compostos
Um programador pode definir tipos de dados compostos para suas necessidades usando a construção 
struct :
 julia> struct GenericStruct  
As estruturas em Julia são imutáveis, ou seja, ao criar uma instância da estrutura, não é mais possível alterar os valores dos campos (mais precisamente, você não pode alterar o endereço dos campos na memória - elementos de campos mutáveis, como 
sv no exemplo acima, podem ser alterados). Estruturas 
mutable struct são criadas pela construção 
mutable struct , cuja sintaxe é igual à das estruturas regulares.
A herança de estruturas no sentido “clássico” não é suportada, no entanto, existe a possibilidade de “herdar” o comportamento combinando tipos compostos em supertipos ou, como são chamados em Julia, tipos abstratos. Os relacionamentos de tipo são expressos como 
A<:B (A é um subtipo de B) e 
A>:B (A é um subtipo de B). Parece algo como isto:
 abstract type NDimPoint end  
Estudo de caso: Polinômios
Um sistema de tipos acoplado ao envio múltiplo é conveniente para expressar conceitos matemáticos. Vejamos um exemplo de uma biblioteca simples para trabalhar com polinômios.
Introduzimos dois tipos de polinômios: "canônico", definido através de coeficientes de potência, e "interpolação", definida por um conjunto de pares (x, f (x)). Para simplificar, consideraremos apenas argumentos válidos.
Para armazenar um polinômio em uma notação usual, é adequada uma estrutura com uma matriz ou uma tupla de coeficientes como um campo. Para ser completamente imutável, faça uma carreata. Assim, o código para definir o tipo abstrato, a estrutura do polinômio e calcular o valor do polinômio em um determinado ponto é bastante simples:
 abstract type AbstractPolynomial end """ Polynomial <: AbstractPolynomial Polynomials written in the canonical form """ struct Polynomial<:AbstractPolynomial degree::Int coeff::NTuple{N, Float64} where N  
Polinômios de interpolação precisam de uma estrutura de representação e método de cálculo diferentes. Em particular, se o conjunto de pontos de interpolação é conhecido antecipadamente, e está planejado calcular o mesmo polinômio em pontos diferentes, 
a fórmula de interpolação de Newton é conveniente:
onde 
n k ( 
x ) são polinômios básicos, 
n 0 ( 
x ) e para 
k > 0
onde 
x i são os nós de interpolação.
A partir das fórmulas acima, pode-se observar que o armazenamento é convenientemente organizado na forma de um conjunto de nós de interpolação 
x i e coeficientes 
c i , e o cálculo pode ser feito de maneira semelhante ao esquema de Horner.
 """ InterpPolynomial <: AbstractPolynomial Interpolation polynomials in Newton's form """ struct InterpPolynomial<:AbstractPolynomial degree::Int xval::NTuple{N, Float64} where N coeff::NTuple{N, Float64} where N end """ evpoly(p::Polynomial, z::Real) Evaluate polynomial `p` at `z` using the Horner's rule """ function evpoly(p::InterpPolynomial, z::Real) ans = p.coeff[p.degree+1] for idx = p.degree:-1:1 ans = ans * (z - p.xval[idx]) + p.coeff[idx] end return ans end 
A função para calcular o valor do polinômio em ambos os casos é chamada de mesma - 
evpoly() - mas aceita diferentes tipos de argumentos.
Além da função de cálculo, seria bom escrever uma função que cria um polinômio a partir de dados conhecidos.
Existem duas técnicas para isso em Julia: construtores externos e construtores internos. Um construtor externo é simplesmente uma função que retorna um objeto do tipo apropriado. Um construtor interno é uma função que é introduzida na descrição da estrutura e substitui o construtor padrão. É recomendável usar o construtor interno para construir polinômios de interpolação, pois
- é mais conveniente obter um polinômio não através dos nós e coeficientes de interpolação, mas através dos nós e valores da função interpolada
- nós de interpolação devem ser distintos
- o número de nós e coeficientes deve corresponder
Escrever um construtor interno no qual essas regras são garantidas para serem seguidas garante que todas as variáveis criadas do tipo 
InterpPolynomial , pelo menos, possam ser processadas corretamente pela função 
evpoly() .
Escrevemos um construtor de polinômios comuns que usa uma matriz unidimensional ou uma tupla de coeficientes como entrada. O construtor do polinômio de interpolação recebe os nós de interpolação e os valores desejados neles e usa o 
método das diferenças divididas para calcular os coeficientes.
 """ Polynomial <: AbstractPolynomial Polynomials written in the canonical form --- Polynomial(v::T) where T<:Union{Vector{<:Real}, NTuple{<:Any, <:Real}}) Construct a `Polynomial` from the list of the coefficients. The coefficients are assumed to go from power 0 in the ascending order. If an empty collection is provided, the constructor returns a zero polynomial. """ struct Polynomial<:AbstractPolynomial degree::Int coeff::NTuple{N, Float64} where N function Polynomial(v::T where T<:Union{Vector{<:Real}, NTuple{<:Any, <:Real}})  
Além da geração real de polinômios, seria bom poder executar operações aritméticas com eles.
Como os operadores aritméticos em Julia são funções comuns, às quais uma notação de infixo é adicionada como açúcar sintático (as expressões 
a + b e 
+(a, b) são válidas e absolutamente idênticas), sua sobrecarga é feita da mesma maneira que a escrita métodos adicionais para suas funções.
O único ponto sutil é que o código do usuário é iniciado a partir do módulo 
Main (namespace) e as funções da biblioteca padrão estão no módulo 
Base , portanto, ao sobrecarregar, você deve importar o módulo 
Base ou escrever o nome completo da função.
Então, adicionamos a adição de um polinômio com um número:
 
Para adicionar dois polinômios comuns, basta adicionar os coeficientes e, ao adicionar o polinômio de interpolação ao outro, você pode encontrar os valores da soma em vários pontos e criar uma nova interpolação a partir deles.
 function Base.:+(p1::Polynomial, p2::Polynomial)  
Da mesma forma, você pode adicionar outras operações aritméticas em polinômios, resultando em sua representação no código em uma notação matemática natural.
Por enquanto é tudo. Vou tentar escrever mais sobre a implementação de outros métodos numéricos.
Na preparação, foram utilizados os seguintes materiais:
- Documentação no idioma Julia: docs.julialang.org
- Plataforma de discussão de idiomas Julia: discourse.julialang.org
- J. Stoer, W. Bulirsch. Introdução à Análise Numérica
- Julia Hub: habr.com/en/hub/julia
- Pense em Julia: benlauwens.imtqy.com/ThinkJulia.jl/latest/book.html