Julia Language
Типы
Поиск…
Синтаксис
- неизменный MyType; поле; поле; конец
- тип MyType; поле; поле; конец
замечания
Типы являются ключевыми для работы Юлии. Важной идеей для производительности является стабильность типа , которая возникает, когда возвращаемый тип функции зависит только от типов, а не от значений его аргументов.
Отправка по типам
В Julia вы можете определить несколько методов для каждой функции. Предположим, что мы определяем три метода одной и той же функции:
foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3
Когда вы решаете, какой метод использовать (называется диспетчером ), Джулия выбирает более конкретный метод, который соответствует типам аргументов:
julia> foo('one')
1
julia> foo(1.0)
2
julia> foo(1)
3
Это облегчает полиморфизм . Например, мы можем легко создать связанный список , указав два неизменных типа с именем Nil
и Cons
. Эти имена традиционно используются для описания пустого списка и непустого списка, соответственно.
abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
first
rest::LinkedList
end
Мы представляем пустой список Nil()
и любые другие списки с помощью Cons(first, rest)
, где first
- это первый элемент связанного списка, а rest
- связанный список, состоящий из всех остальных элементов. Например, список [1, 2, 3]
будет представлен как
julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))
Список пуст?
Предположим, мы хотим расширить функцию стандартного библиотечного isempty
, которая работает с множеством различных коллекций:
julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...
Мы можем просто использовать синтаксис функции отправки и определить две дополнительные методы isempty
. Так как эта функция из Base
модуля, мы должны квалифицировать как Base.isempty
для того , чтобы продлить его.
Base.isempty(::Nil) = true
Base.isempty(::Cons) = false
Здесь нам вообще не нужны значения аргументов, чтобы определить, пуст ли список. Просто одного типа достаточно, чтобы вычислить эту информацию. Джулия позволяет нам опускать имена аргументов, сохраняя только аннотацию типа, если нам не нужны их значения.
Мы можем проверить , isempty
наши isempty
методы:
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())))))
и действительно, количество методов для isempty
увеличилось на 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
Ясно, что определение того, является ли связанный список пустым или нет, является тривиальным примером. Но это приводит к чему-то более интересному:
Как долго этот список?
Функция length
из стандартной библиотеки дает нам длину коллекции или определенные итерации . Существует множество способов реализации length
для связанного списка. В частности, используя while
цикл, вероятно , самый быстрый и наиболее эффективно использует память в Джулию. Но преждевременной оптимизации следует избегать, так что давайте предположим, что наш связанный список не должен быть эффективным. Каков самый простой способ написать функцию length
?
Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)
Первое определение прост: пустой список имеет длину 0
. Второе определение также легко читается: чтобы подсчитать длину списка, мы подсчитываем первый элемент, а затем подсчитываем длину остальной части списка. Мы можем проверить этот метод так же , как мы тестировали 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
Следующие шаги
Этот пример игрушек довольно далек от реализации всех функций, которые желательны в связанном списке. Его отсутствует, например, итерационный интерфейс. Тем не менее, это иллюстрирует, как отправка может быть использована для написания короткого и четкого кода.
Неизменяемые типы
Самый простой составной тип - неизменный тип. Экземпляры неизменяемых типов, например кортежи , являются значениями. Их поля не могут быть изменены после их создания. Во многих отношениях неизменяемый тип подобен Tuple
с именами для самого типа и для каждого поля.
Типы Singleton
Композитные типы, по определению, содержат ряд более простых типов. В Julia это число может быть нулем; то есть непреложный типа допускается не содержат никаких полей. Это сопоставимо с пустым кортежем ()
.
Почему это может быть полезно? Такие неизменные типы известны как «одноэлементные типы», поскольку только один из них может когда-либо существовать. Значения таких типов известны как «одиночные значения». Стандартная библиотека Base
содержит много таких одноэлементных типов. Вот краткий список:
-
Void
, типnothing
. Мы можем проверить, чтоVoid.instance
(который является специальным синтаксисом для получения одноэлементного значения одноэлементного типа) действительноnothing
. - Любой тип мультимедиа, такой как
MIME"text/plain"
, представляет собой одноэлементный тип с одним экземпляромMIME("text/plain")
. -
Irrational{:π}
,Irrational{:e}
,Irrational{:φ}
и подобные типы являются одноточечными типами, а их одноэлементные экземпляры - иррациональные значенияπ = 3.1415926535897...
и т. Д. - Характеристики размера итератора
Base.HasLength
,Base.HasShape
,Base.IsInfinite
иBase.SizeUnknown
- это все одноэлементные типы.
- В версии 0.5 и более поздней, каждая функция представляет собой одноэлементный экземпляр одноэлементного типа! Как и любое другое значение singleton, мы можем восстановить функцию
sin
, например, изtypeof(sin).instance
.
Поскольку они не содержат ничего, одноэлементные типы невероятно легкие, и их часто можно оптимизировать компилятором, чтобы не иметь накладных расходов во время работы. Таким образом, они идеально подходят для черт, специальных значений тегов и для таких функций, как функции, на которые стоит специализироваться.
Чтобы определить одноэлементный тип,
julia> immutable MySingleton end
Чтобы определить пользовательскую печать для одноэлементного типа,
julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")
Чтобы получить доступ к экземпляру singleton,
julia> MySingleton.instance
MySingleton()
Часто это присваивается константе:
julia> const sing = MySingleton.instance
MySingleton()
Типы оберток
Если неизменяемые типы нулевого поля интересны и полезны, то, возможно, еще более полезны однотипные неизменные типы. Такие типы обычно называют «типами обертки», потому что они обертывают некоторые базовые данные, обеспечивая альтернативный интерфейс к указанным данным. Примером типа оболочки в Base
является String
. Мы будем определять аналогичный тип для String
именем MyString
. Этот тип будет поддерживаться векторным (одномерный массив ) байтов ( UInt8
).
Во-первых, само определение типа и некоторые настройки показывают:
immutable MyString <: AbstractString
data::Vector{UInt8}
end
function Base.show(io::IO, s::MyString)
print(io, "MyString: ")
write(io, s.data)
return
end
Теперь наш тип MyString
готов к использованию! Мы можем прокормить его некоторыми сырыми данными UTF-8, и он отображает, как нам нравится:
julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!
Очевидно, что этот тип строки требует большой работы, прежде чем он станет таким же удобным, как и тип Base.String
.
Истинные составные типы
Возможно, чаще всего многие неизменяемые типы содержат более одного поля. Примером может служить стандартная библиотека Rational{T}
type, которая содержит два fieds: поле num
для числителя и поле den
для знаменателя. Эту модель такого типа довольно просто:
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)
Мы успешно внедрили конструктор, который упрощает наши рациональные числа:
julia> MyRational(10, 6)
MyRational{Int64}(5,3)