Até agora, o currículo não disse muito sobre tipos, mas claramente eles existem na Julia:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
Nunca especificámos os tipos, mas a Julia atribuiu-os na mesma.
types, que são centrais no seu design.Type Inference.O compilador JIT vai percorrer (todo) o código, ver como uma variável é usada e inferir um tipo predefinido adequado, compatível com esse uso.
Confiar na inferência de tipos é suficiente para resolver exercícios simples de tutorial, mas em programas maiores é provável que precises de um controlo mais preciso.
Na maioria dos processadores modernos, um número inteiro assume por defeito o tipo Int64.
Vimos no conceito Numbers que um valor pode ser convertido para um tipo específico diferente do predefinido.
julia> x = Int16(42)
42
julia> typeof(x)
Int16
No entanto, a variável x pode ainda ser reatribuída a um tipo diferente:
julia> x = "changed"
"changed"
julia> typeof(x)
String
Isto é type instability, que:
Em vez disso, podemos definir o tipo de x com o operador :::
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
Agora y é, e será sempre, do tipo Int16.
Assim, o compilador sabe quantos bytes deve reservar para ela e pode otimizar o resto do código contando com um tipo estável.
Neste aspeto, a variável é mais ou menos semelhante às de uma linguagem de tipos estáticos como C.
Int64, Int16, String, Char: de onde é que estes tipos "vêm".
Em muitas linguagens orientadas a objetos (OO), cada tipo é uma classe, a criação de subclasses organiza-as numa hierarquia de classes e os métodos de classe definem os comportamentos.
O Java e o Ruby são exemplos óbvios deste padrão, mas até o Python é semelhante a nível interno.
A Julia não tem classes.
A razão documentada é que as funcionalidades de OO interferem com o compilador JIT e prejudicam o desempenho em tempo de execução.
E, no entanto, olha para este código:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
Com mais alguns detalhes:
Int16 é um tipo e podemos criar variáveis deste tipo.Int16 é um subtipo de Signed e a função supertype() mostra-nos isso.Integer, Real e Number até Any, no topo, e subtypes() lista-nos este ramo da hierarquia.Todos os ramos terminam em Any, que é único por ser o seu próprio supertipo.
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
Portanto, a Julia não tem uma hierarquia de class, mas tem uma hierarquia de type.
Mais tarde, tentaremos perceber como isto funciona, mas ainda há muito mais para explorar primeiro.
Podemos usar typeof() para testar a igualdade da forma habitual.
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
A igualdade de tipos tem de ser exata, porque esta forma de comparação não tem em conta a hierarquia de tipos.
De forma mais flexível, isa diz-nos se um valor tem o mesmo tipo que um comparador ou um subtipo desse tipo.
Pode ser usado tanto na forma de função como na forma infixa.
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
Repara que isa espera um value à esquerda, não um type.
Tentar comparar dois tipos desta forma dá resultados inesperados.
O operador correto é <:, que vamos ver muito mais vezes em conceitos futuros.
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
Vimos que a hierarquia de tipos forma uma estrutura em árvore (no sentido da informática, com a raiz no topo).
Cada item da árvore é um node, e estes podem dividir-se em categorias:
abstract.concrete.julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
Esta distinção é importante, porque só os tipos concretos podem ser instantiated como variáveis.
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
Repara que tentar usar um tipo abstrato não dá mensagem de erro (neste caso), mas o compilador cria um tipo concreto adequado: Int64 em vez de Integer.
Neste exercício, vais fazer um pouco de engenharia de dados, ao pré-processar dados de notas. Uma escola com muitos, muitos, muitos (demasiados?) alunos está a tentar fazer uma análise de dados às notas recebidas pelos alunos e quer fazê-lo de forma eficiente, por isso a ideia é minimizar e organizar os dados.
A escala de notas costuma ser de 0 a 10, por isso o mais eficiente é usar um tipo de dados com apenas oito bits por nota.
UInt8.Int8.Depois de implementares a função que converte as notas, vais ter de escrever outra para tratar as coleções que as contêm, rebaixando as notas e devolvendo-as por ordem decrescente.
Vectors ordenados por ordem crescente para guardar as notas, porque são meticulosos com os seus dados.Sets, que não estão ordenados, para guardar as notas, porque são um pouco mais preguiçosos.Em ambas as funções, vais ter de tratar entradas inválidas lançando um MethodError.
O tratamento de exceções será abordado num Conceito mais adiante, por isso, para efeitos deste exercício, podes usar a seguinte sintaxe:
throw(MethodError(f, args))
Em que f é a função e args é um tuplo com os argumentos passados à função.
Na função demote(n), f é demote e args é (n,):
throw(MethodError(demote, (n,)))
Implementa o método demote(n).
Float64, deve arredondar para cima até ao número inteiro mais próximo e devolver o tipo de dados UInt8.Integer arbitrário, deve devolver o mesmo inteiro com o tipo de dados Int8.julia> demote(4.2)::UInt8
5
julia> demote(4)::Int8
4
julia> demote("hi")
MethodError: no method matching demote(::String) #output truncated
Implementa o método preprocess(coll).
Vector, deve rebaixar todos os números e inverter o vetor.Set, deve rebaixar todos os números e devolver um vetor ordenado por ordem decrescente.julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
3
2
1
julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
6
4
3
julia> preprocess(42)
MethodError: no method matching preprocess(::Int64) #output truncated
Inscreve-te no Exercism para aprenderes e dominares Julia com 35 conceitos128 exercícios, e mentoria humana real, tudo grátis.