Julia Language
Les types
Recherche…
Syntaxe
- MyType immuable champ; champ; fin
- tapez MyType; champ; champ; fin
Remarques
Les types sont la clé de la performance de Julia. Une idée importante pour la performance est la stabilité des types , qui se produit lorsque le type renvoyé par une fonction dépend uniquement des types, et non des valeurs, de ses arguments.
Envoi sur types
Sur Julia, vous pouvez définir plusieurs méthodes pour chaque fonction. Supposons que nous définissions trois méthodes de la même fonction:
foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3
Lors du choix de la méthode à utiliser (appelée dispatch ), Julia choisit la méthode plus spécifique correspondant aux types d'arguments:
julia> foo('one')
1
julia> foo(1.0)
2
julia> foo(1)
3
Cela facilite le polymorphisme . Par exemple, nous pouvons facilement créer une liste chaînée en définissant deux types immuables, nommés Nil
et Cons
. Ces noms sont traditionnellement utilisés pour décrire une liste vide et une liste non vide, respectivement.
abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
first
rest::LinkedList
end
Nous représenterons la liste vide par Nil()
et par toute autre liste par Cons(first, rest)
, où first
est le premier élément de la liste liée et le rest
est la liste chaînée composée de tous les éléments restants. Par exemple, la liste [1, 2, 3]
sera représentée comme
julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))
La liste est-elle vide?
Supposons que nous voulions étendre la fonction isempty
la bibliothèque standard, qui fonctionne sur différentes collections:
julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...
Nous pouvons simplement utiliser la syntaxe de répartition de la fonction et définir deux méthodes supplémentaires d' isempty
. Comme cette fonction provient du module Base
, nous devons la qualifier de Base.isempty
afin de l'étendre.
Base.isempty(::Nil) = true
Base.isempty(::Cons) = false
Ici, nous n'avons pas du tout besoin des valeurs d'argument pour déterminer si la liste est vide. Le seul type suffit pour calculer cette information. Julia nous permet d'omettre les noms des arguments, en gardant uniquement leur annotation de type, si nous n'avons pas besoin d'utiliser leurs valeurs.
Nous pouvons tester que nos méthodes isempty
fonctionnent:
julia> using Base.Test
julia> @test isempty(Nil())
Test Passed
Expression: isempty(Nil())
julia> @test !isempty(Cons(1, Cons(2, Cons(3, Nil()))))
Test Passed
Expression: !(isempty(Cons(1,Cons(2,Cons(3,Nil())))))
et en effet le nombre de méthodes pour isempty
a augmenté de 2
:
julia> methods(isempty)
# 31 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
De toute évidence, déterminer si une liste chaînée est vide ou non est un exemple trivial. Mais cela mène à quelque chose de plus intéressant:
Combien de temps dure la liste?
La fonction length
de la bibliothèque standard nous donne la longueur d'une collection ou de certaines itérations . Il existe plusieurs façons de mettre en œuvre une length
pour une liste chaînée. En particulier, en utilisant un while
boucle est probablement le plus rapide et le plus efficace mémoire de Julia. Mais l' optimisation prématurée doit être évitée, alors supposons que notre liste chaînée ne soit pas efficace. Quelle est la manière la plus simple d'écrire une fonction de length
?
Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)
La première définition est simple: une liste vide a une longueur de 0
. La deuxième définition est également facile à lire: pour compter la longueur d'une liste, nous comptons le premier élément, puis comptons la longueur du reste de la liste. Nous pouvons tester cette méthode de la même manière que nous avons testé isempty
:
julia> @test length(Nil()) == 0
Test Passed
Expression: length(Nil()) == 0
Evaluated: 0 == 0
julia> @test length(Cons(1, Cons(2, Cons(3, Nil())))) == 3
Test Passed
Expression: length(Cons(1,Cons(2,Cons(3,Nil())))) == 3
Evaluated: 3 == 3
Prochaines étapes
Cet exemple de jouet est loin d'implémenter toutes les fonctionnalités souhaitées dans une liste chaînée. Il manque, par exemple, l'interface d'itération. Cependant, il illustre comment la répartition peut être utilisée pour écrire un code court et clair.
Types immuables
Le type composite le plus simple est un type immuable. Les instances de types immuables, comme les tuples , sont des valeurs. Leurs champs ne peuvent pas être modifiés après leur création. À bien des égards, un type immuable est comme un Tuple
avec des noms pour le type lui-même et pour chaque champ.
Types singleton
Les types composites, par définition, contiennent un certain nombre de types plus simples. En Julia, ce nombre peut être zéro; c'est-à-dire qu'un type immuable est autorisé à ne contenir aucun champ. Ceci est comparable au tuple vide ()
.
Pourquoi cela pourrait-il être utile? Ces types immuables sont connus sous le nom de "types singleton", car une seule d'entre eux pourrait exister. Les valeurs de ces types sont appelées "valeurs singleton". La bibliothèque standard Base
contient de nombreux types singleton. Voici une brève liste:
-
Void
, le type denothing
. Nous pouvons vérifier queVoid.instance
(qui est une syntaxe spéciale pour récupérer la valeur singleton d'un type singleton) n'est en effetnothing
. - Tout type de média, tel que
MIME"text/plain"
, est un type singleton avec une seule instance,MIME("text/plain")
. - Les types
Irrational{:π}
,Irrational{:e}
,Irrational{:φ}
et similaires sont des types singleton, et leurs instances singleton sont les valeurs irrationnellesπ = 3.1415926535897...
, etc. - Les traits de taille de l'itérateur
Base.HasLength
,Base.HasShape
,Base.IsInfinite
etBase.SizeUnknown
sont tous des types singleton.
- Dans la version 0.5 et les versions ultérieures, chaque fonction est une instance singleton d'un type singleton! Comme toute autre valeur de singleton, nous pouvons récupérer la fonction
sin
, par exemple, detypeof(sin).instance
.
Comme ils ne contiennent rien, les types singleton sont incroyablement légers et ils peuvent souvent être optimisés par le compilateur pour ne pas avoir de surcharge d'exécution. Ainsi, ils sont parfaits pour les traits, les valeurs d'étiquettes spéciales et pour des choses comme les fonctions que l'on aimerait se spécialiser.
Pour définir un type de singleton,
julia> immutable MySingleton end
Pour définir une impression personnalisée pour le type singleton,
julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")
Pour accéder à l'instance singleton,
julia> MySingleton.instance
MySingleton()
Souvent, on assigne ceci à une constante:
julia> const sing = MySingleton.instance
MySingleton()
Types d'emballage
Si les types immuables du champ zéro sont intéressants et utiles, alors les types immuables à un champ sont peut-être encore plus utiles. De tels types sont communément appelés "types de wrappers" car ils encapsulent certaines données sous-jacentes, fournissant une interface alternative auxdites données. Un exemple de type de wrapper dans Base
est String
. Nous allons définir un type similaire à String
, nommé MyString
. Ce type sera soutenu par un vecteur ( tableau à une dimension) d'octets ( UInt8
).
Tout d'abord, la définition de type elle-même et certains éléments personnalisés:
immutable MyString <: AbstractString
data::Vector{UInt8}
end
function Base.show(io::IO, s::MyString)
print(io, "MyString: ")
write(io, s.data)
return
end
Maintenant, notre type MyString
est prêt à être utilisé! Nous pouvons lui fournir des données UTF-8 brutes, et cela s’affiche comme nous l’aimons pour:
julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!
Evidemment, ce type de chaîne nécessite beaucoup de travail avant de devenir aussi utilisable que le type Base.String
.
Véritables types composites
Le plus souvent, de nombreux types immuables contiennent plus d'un champ. Un exemple est le type de bibliothèque standard Rational{T}
, qui contient deux champs: un champ num
pour le numérateur et un champ den
pour le dénominateur. Il est assez simple d'émuler ce type de conception:
immutable MyRational{T}
num::T
den::T
MyRational(n, d) = (g = gcd(n, d); new(n÷g, d÷g))
end
MyRational{T}(n::T, d::T) = MyRational{T}(n, d)
Nous avons implémenté avec succès un constructeur qui simplifie nos nombres rationnels:
julia> MyRational(10, 6)
MyRational{Int64}(5,3)