类别
类型系统传统上分为两个完全不同的阵营:静态类型系统,每个程序表达式在程序执行之前都必须有一个可计算的类型;动态类型系统,在程序操作的实际值可用时,直到运行时才知道类型。 面向对象允许在静态类型语言中具有一定的灵活性,它允许在编译时在编写代码时不需要知道精确的值类型。 编写可以对不同类型进行操作的代码的能力称为多态性。 经典动态类型语言中的所有代码都是多态的:只有通过显式检查类型,或者当对象在运行时不支持操作时,任何值的类型才会受到限制。
Julia的类型系统是动态的,但通过指示某些值是特定类型,获得了静态类型系统的一些优点。 这对生成高效的代码有很大的帮助,但更重要的是,它允许函数参数类型的方法调度与语言深度集成。 方法调度详细探讨在 方法,但植根于这里提出的类型系统。
Julia中省略类型时的默认行为是允许值为任何类型。 因此,可以编写许多有用的Julia函数,而无需显式使用类型。 但是,当需要额外的表现力时,很容易逐渐将显式类型注释引入到以前的"无类型"代码中。 添加注释有三个主要目的:利用Julia强大的多重调度机制,提高人类可读性,以及捕获程序员错误。
用……类型系统,它是:动态,主格和参数。 泛型类型可以参数化,类型之间的层次关系是https://en.wikipedia.org/wiki/Nominal_type_system[明确声明],而不是https://en.wikipedia.org/wiki/Structural_type_system[由兼容结构暗示]。 Julia类型系统的一个特别显着的特点是具体类型可能不会相互子类型:所有具体类型都是最终类型,并且可能只具有抽象类型作为其超类型。 虽然这起初可能看起来限制不当,但它有许多有益的后果,但缺点却少得令人惊讶。 事实证明,能够继承行为比能够继承结构要重要得多,并且继承两者在传统的面向对象语言中造成了显着的困难。 Julia类型系统的其他高级方面应该在前面提到:
*对象值和非对象值之间没有划分:Julia中的所有值都是真正的对象,其类型属于单个全连接类型图,其中所有节点都与类型一样是一流的。
*没有有意义的"编译时类型"概念:值具有的唯一类型是程序运行时的实际类型。 这在面向对象语言中被称为"运行时类型",其中静态编译与多态性的结合使这种区别变得重要。
*只有值,而不是变量,才有类型-变量只是绑定到值的名称,尽管为了简单起见,我们可以说"变量的类型"是"变量所指值的类型"的简写。
*抽象和具体类型都可以由其他类型参数化。 它们也可以通过符号、任何类型的值来参数化。 轨道,轨道返回true(本质上,像c类型或c类型那样存储的数字和boool之类的东西 结构体没有指向其他对象的指针),也可以通过元组。 当不需要引用或限制类型参数时,可以省略它们。
Julia的类型系统设计得强大而富有表现力,但清晰,直观和不引人注目。 许多Julia程序员可能永远不会觉得需要编写明确使用类型的代码。 但是,某些类型的编程使用声明的类型变得更清晰,更简单,更快和更健壮。
类型声明
该 :: 运算符可用于将类型注释附加到程序中的表达式和变量。 这样做有两个主要原因:
-
作为一个断言,以帮助确认您的程序按照您期望的方式工作,并且
-
为编译器提供额外的类型信息,然后在某些情况下可以提高性能。
当附加到计算值的表达式时, :: 运算符被读作"is an instance of"。 它可以在任何地方用于断言左侧表达式的值是右侧类型的实例。 当右侧的类型是具体的时,左侧的值必须具有该类型作为其实现-回想一下,所有具体类型都是final,因此没有实现是任何其他类型的子类型。 当类型为abstract时,值就足以由作为abstract类型的子类型的具体类型实现。 如果类型断言不为true,则抛出异常,否则返回左侧值:
julia> (1+2)::AbstractFloat
ERROR: TypeError: in typeassert, expected AbstractFloat, got a value of type Int64
julia> (1+2)::Int
3
这允许将类型断言附加到就地的任何表达式。
当附加到赋值左侧的变量时,或作为赋值的一部分 本地 声明, :: operator的意思有点不同:它声明变量始终具有指定的类型,就像静态类型语言(如C)中的类型声明一样。 转换/转换:
julia> function foo()
x::Int8 = 100
x
end
foo (generic function with 1 method)
朱莉娅>x=foo()
100
朱莉娅>类型(x)
Int8
如果对变量的某个赋值意外更改了其类型,则此功能可用于避免可能发生的性能"gotchas"。
此"声明"行为仅在特定上下文中发生:
local x::Int8 # in a local declaration
x::Int8 = 10 # as the left-hand side of an assignment
并且适用于整个当前范围,甚至在声明之前。
从Julia1.8开始,类型声明现在可以在全局作用域中使用,即类型注释可以添加到全局变量中,以使访问它们的类型稳定。
julia> x::Int = 10
10
julia> x = 3.5
ERROR: InexactError: Int64(3.5)
julia> function foo(y)
global x = 15.8 # throws an error when foo is called
return x + y
end
foo (generic function with 1 method)
julia> foo(10)
ERROR: InexactError: Int64(15.8)
声明也可以附加到函数定义:
function sinc(x)::Float64
if x == 0
return 1
end
return sin(pi*x)/(pi*x)
end
从该函数返回的行为就像对具有声明类型的变量的赋值:该值始终转换为 漂浮64.
抽象类型
抽象类型不能被实例化,并且只能作为类型图中的节点,从而描述相关的具体类型集:那些作为其后代的具体类型。 我们从抽象类型开始,即使它们没有实例化,因为它们是类型系统的骨干:它们形成了概念层次结构,这使得Julia的类型系统不仅仅是一个对象实现的集合。
回想一下,在 整数和浮点数,我们介绍了各种具体类型的数值: Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, 漂浮物16, 漂浮物32,和 漂浮64. 虽然它们具有不同的表示大小, Int8, Int16, Int32, Int64 和 Int128 它们都有一个共同点,即它们是有符号整数类型。 同样 UInt8, UInt16, UInt32, UInt64 和 UInt128 都是无符号整数类型,而 漂浮物16, 漂浮物32 和 漂浮64 在浮点类型而不是整数方面是不同的。 一段代码通常是有意义的,例如,只有当它的参数是某种整数时,而不是真正依赖于整数的特定_kind_。 例如,最大共同点算法适用于所有类型的整数,但不适用于浮点数。 抽象类型允许构造类型层次结构,提供具体类型可以适应的上下文。 例如,这允许您轻松编程为整数的任何类型,而不将算法限制为特定类型的整数。
抽象类型使用 抽象类型关键字。 声明抽象类型的一般语法是:
abstract type «name» end abstract type «name» <: «supertype» end
该 抽象类型 关键字引入了一个新的抽象类型,其名称由 "姓名". 此名称可以选择后跟 <:和一个已经存在的类型,表示新声明的抽象类型是这个"父"类型的子类型。
当没有给出超类型时,默认的超类型是 任何 --预定义的抽象类型,所有对象都是实例,所有类型都是子类型。 在类型理论中, 任何 通常被称为"顶部",因为它位于类型图的顶点。 Julia还有一个预定义的抽象"底部"类型,位于类型图的最低点,它被写成 工会{}. It is the exact opposite of Any: no object is an instance of Union{} 所有类型都是 联合{}.
让我们考虑一些构成Julia数值层次结构的抽象类型:
abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
abstract type Integer <: Real end
abstract type Signed <: Integer end
abstract type Unsigned <: Integer end
该 电话号码类型是直接的子类型 任何,而 真实的 是它的孩子。 反过来, 真实的 有两个孩子(它有更多,但这里只显示两个;我们稍后会到达其他人): xref:base/numbers.adoc#Core.Integer[整数和 [医抽象浮],将世界分为整数的表示和实数的表示。 实数的表示包括浮点类型,但也包括其他类型,如理据。 [医]抽象浮 仅包括实数的浮点表示。 整数进一步细分为 签署和 未签名品种。
该 <: 运算符通常表示"是"的子类型,并且在上述声明中使用,将右侧类型声明为新声明类型的直接超类型。 它也可以在表达式中用作返回的子类型运算符 真的 当其左操作数是其右操作数的子类型时:
julia> Integer <: Number
true
julia> Integer <: AbstractFloat
false
抽象类型的一个重要用途是为具体类型提供默认实现。 举一个简单的例子,考虑:
function myplus(x,y)
x+y
end
首先要注意的是,上面的参数声明等效于 x::任何 和 y::任何. 当调用此函数时,如 myplus(2,5),调度程序选择命名的最具体的方法 myplus公司 这与给定的参数匹配。 (见 方法有关多个调度的更多信息。)
假设没有找到比上面更具体的方法,Julia next内部定义并编译一个名为 myplus公司 专门为两个 Int型 基于上面给出的泛型函数的参数,即它隐式定义和编译:
function myplus(x::Int,y::Int)
x+y
end
最后,它调用了这个特定的方法。
因此,抽象类型允许程序员编写泛型函数,这些函数以后可以被许多具体类型的组合用作默认方法。 由于多次调度,程序员可以完全控制是否使用默认或更具体的方法。
需要注意的一点是,如果程序员依赖于参数是抽象类型的函数,则性能不会损失,因为它是为调用它的具体参数类型的每个元组重新编译的。 (但是,如果函数参数是抽象类型的容器,则可能存在性能问题;请参阅 性能提示。)
原始类型
|
警告在新的复合类型中包装现有的基元类型几乎总是比定义自己的基元类型更可取。 |
此功能的存在是为了允许Julia引导LLVM支持的标准基元类型。 一旦定义了它们,就很少有理由定义更多。
基元类型是一种具体类型,其数据由普通的旧位组成。 基本类型的经典示例是整数和浮点值。 与大多数语言不同,Julia允许您声明自己的原始类型,而不是只提供一组固定的内置类型。 实际上,标准基元类型都是在语言本身中定义的:
primitive type Float16 <: AbstractFloat 16 end
primitive type Float32 <: AbstractFloat 32 end
primitive type Float64 <: AbstractFloat 64 end
primitive type Bool <: Integer 8 end
primitive type Char <: AbstractChar 32 end
primitive type Int8 <: Signed 8 end
primitive type UInt8 <: Unsigned 8 end
primitive type Int16 <: Signed 16 end
primitive type UInt16 <: Unsigned 16 end
primitive type Int32 <: Signed 32 end
primitive type UInt32 <: Unsigned 32 end
primitive type Int64 <: Signed 64 end
primitive type UInt64 <: Unsigned 64 end
primitive type Int128 <: Signed 128 end
primitive type UInt128 <: Unsigned 128 end
声明原始类型的一般语法是:
primitive type «name» «bits» end primitive type «name» <: «supertype» «bits» end
位数表示类型需要多少存储空间,名称为新类型命名。 基元类型可以选择声明为某个超类型的子类型。 如果省略了超类型,则该类型默认为具有 任何 作为它的直接超类型。 《 布尔因此,上面意味着一个布尔值需要八位来存储,并且具有 整数作为其直接超类型。 目前,只支持8位的倍数大小,您可能会遇到LLVM错误,其大小与上述大小不同。 因此,布尔值虽然只需要一个位,但不能声明为小于8位。
类型 布尔, Int8和 UInt8都有相同的表示:它们是八位内存块。 然而,由于Julia的类型系统是主格的,尽管它们具有相同的结构,但它们是不可互换的。 它们之间的一个根本区别是它们具有不同的超类型: 布尔的直接超类型是 整数, Int8’s是 xref:base/numbers.adoc#Core.Signed[`签署,和 UInt8’是 xref:base/numbers.adoc#Core.Unsigned[`未签名. 之间的所有其他差异 布尔, Int8,和 UInt8是行为的问题-当给定这些类型的对象作为参数时,定义函数的行为方式。 这就是为什么主格类型系统是必要的:如果结构决定类型,这反过来又决定行为,那么就不可能使 布尔行为与……不同 Int8或 UInt8.
复合类型
复合类型在各种语言中称为记录、结构或对象。 复合类型是命名字段的集合,其实例可以被视为单个值。 在许多语言中,复合类型是唯一一种用户定义类型,并且它们也是迄今为止Julia中最常用的用户定义类型。
在主流的面向对象语言中,如C++,Java,Python和Ruby,复合类型也有与它们相关联的命名函数,组合称为"对象"。 在更纯粹的面向对象语言中,如Ruby或Smalltalk,所有值都是对象,无论它们是否是复合的。 在不那么纯粹的面向对象语言中,包括C++ 而Java中,一些值,如整数和浮点值,不是对象,而用户定义的复合类型的实例是具有关联方法的真正对象。 在Julia中,所有值都是对象,但函数不会与它们操作的对象捆绑在一起。 这是必要的,因为Julia选择多个调度使用函数的哪个方法,这意味着在选择方法时会考虑函数参数的_all_类型,而不仅仅是第一个(参见 方法有关方法和调度的更多信息)。 因此,函数只"属于"它们的第一个参数是不合适的。 将方法组织到函数对象中,而不是在每个对象的"内部"具有命名的方法袋,最终成为语言设计的一个非常有益的方面。
复合类型与 结构体关键字后跟一个字段名称块,可以使用 :: 操作员:
julia> struct Foo
bar
baz::Int
qux::Float64
end
没有类型注释的字段默认为 任何,并且可以相应地保持任何类型的值。
类型的新对象 [医]脚 是通过应用 [医]脚 将对象(如函数)类型为其字段的值:
julia> foo = Foo("Hello, world.", 23, 1.5)
Foo("Hello, world.", 23, 1.5)
julia> typeof(foo)
Foo
当一个类型像函数一样应用时,它被称为_constructor_。 自动生成两个构造函数(这些构造函数称为_default constructors_)。 一个接受任何参数和调用 转换/转换将它们转换为字段的类型,另一个接受与字段类型完全匹配的参数。 生成这两个定义的原因是,这样可以更轻松地添加新定义,而不会无意中替换默认构造函数。
自 酒吧 字段在类型上是无约束的,任何值都可以。 但是,对于 巴兹 必须可转换为 Int型:
julia> Foo((), 23.5, 1)
ERROR: InexactError: Int64(23.5)
Stacktrace:
[...]
您可以使用 字段名称功能。
julia> fieldnames(Foo)
(:bar, :baz, :qux)
您可以使用传统的方法访问复合对象的字段值 foo。酒吧 符号:
julia> foo.bar
"Hello, world."
julia> foo.baz
23
julia> foo.qux
1.5
声明为 结构体 are_immutable_;它们在构建后不能被修改。 起初这可能看起来很奇怪,但它有几个优点:
*它可以更有效率。 一些结构可以有效地打包到数组中,在某些情况下,编译器能够完全避免分配不可变对象。 *不可能违反类型的构造函数提供的不变量。 *使用不可变对象的代码可以更容易推理。
不可变对象可能包含可变对象(如数组)作为字段。 那些包含的对象将保持可变;只有不可变对象本身的字段不能更改为指向不同的对象。
如果需要,可以使用关键字声明可变复合对象 可变结构,将在下一节中讨论。
如果不可变结构的所有字段都不可区分(===)然后包含这些字段的两个不可变值也是不可区分的:
julia> struct X
a::Int
b::Float64
end
julia> X(1, 2) === X(1, 2)
true
对于许多用户定义类型 X,您可能需要定义一个方法 基地。broadcastable(x::X)=Ref(x)以便该类型的实例充当0维"标量" 广播。
可变复合类型
如果复合类型声明为 可变结构 而不是 结构体,然后可以修改它的实例:
julia> mutable struct Bar
baz
qux::Float64
end
julia> bar = Bar("Hello", 1.5);
julia> bar.qux = 2.0
2.0
julia> bar.baz = 1//2
1//2
字段和用户之间的额外接口可以通过以下方式提供 实例属性。 这授予了更多的控制可以访问和修改什么使用 酒吧。巴兹 符号。
为了支持mutation,这类对象一般都是在堆上分配的,并且有稳定的内存地址。 可变对象就像一个小容器,随着时间的推移可能会保存不同的值,因此只能通过其地址可靠地识别。 相反,不可变类型的实例与特定字段值相关联---字段值单独告诉您有关对象的所有信息。 在决定是否使类型可变时,询问具有相同字段值的两个实例是否被视为相同,或者它们是否需要随着时间的推移独立更改。 如果它们被认为是相同的,那么类型可能应该是不可变的。
回顾一下,两个基本属性定义了Julia中的不变性:
*不允许修改不可变类型的值。 **对于位类型,这意味着一旦设置值的位模式将永远不会改变,该值是位类型的标识。 **对于复合类型,这意味着其字段值的标识永远不会更改。 当字段是位类型时,这意味着它们的位永远不会改变,对于值是可变类型(如数组)的字段,这意味着字段将始终引用相同的可变值,即使可变值的内容本身可能被修改。 *具有不可变类型的对象可以由编译器自由复制,因为它的不可变性使得无法以编程方式区分原始对象和副本。 **特别是,这意味着足够小的不可变值(如整数和浮点数)通常传递给寄存器中的函数(或堆栈分配)。 **可变值,另一方面是堆分配的,并作为指向堆分配值的指针传递给函数,除非编译器确信没有办法告诉这不是正在发生的事情。
在已知可变结构的一个或多个字段不可变的情况下,可以使用 康斯特 如下所示。 这可以实现不可变结构体的某些优化,但不是全部优化,并可用于对标记为 康斯特.
|
兼容性
朱莉娅1.8 |
julia> mutable struct Baz
a::Int
const b::Float64
end
julia> baz = Baz(1, 1.5);
julia> baz.a = 2
2
julia> baz.b = 2.0
ERROR: setfield!: const field .b of type Baz cannot be changed
[...]
声明的类型
前面几节讨论的三种类型(抽象、原始、复合)实际上都是密切相关的。 它们共享相同的关键属性:
*它们是明确声明的。 *他们有名字。 *它们已明确声明超类型。 *它们可能有参数。
由于这些共享属性,这些类型在内部表示为相同概念的实例, 数据类型,这是任何这些类型的类型:
julia> typeof(Real)
DataType
julia> typeof(Int)
DataType
A 数据类型 可以是抽象的或具体的。 如果它是具体的,它具有指定的大小,存储布局和(可选)字段名称。 因此,原始类型是 数据类型 具有非零大小,但没有字段名称。 复合类型是 数据类型 具有字段名称或为空(零大小)。
系统中的每个具体值都是一些值的一个实例 数据类型.
类型工会
类型联合是一种特殊的抽象类型,它将其任何参数类型的所有实例作为对象,使用特殊类型构造 工会关键字:
julia> IntOrString = Union{Int,AbstractString}
Union{Int64, AbstractString}
julia> 1 :: IntOrString
1
julia> "Hello!" :: IntOrString
"Hello!"
julia> 1.0 :: IntOrString
ERROR: TypeError: in typeassert, expected Union{Int64, AbstractString}, got a value of type Float64
许多语言的编译器都有一个内部的联合构造来推理类型;朱莉娅只是把它暴露给程序员。 Julia编译器能够在存在 工会 具有少量类型的类型脚注:1["小"由 max_union_分片 配置,目前默认为4。],通过为每种可能的类型在单独的分支中生成专门的代码。
一个特别有用的例子 工会 类型为 工会{T, Nothing},在哪里 T 可以是任何类型和 什么都没有是唯一实例为对象的单例类型 什么都没有. 这种模式相当于Julia 脧锚脧赂`可空`, 选项 或 也许吧其他语言的类型。 将函数参数或字段声明为 工会{T, Nothing} 允许将其设置为类型的值 T,或至 什么都没有 以表示没有值。 见 此常见问题条目以获取更多信息。
参数类型
Julia的类型系统的一个重要而强大的特性是它是参数化的:类型可以接受参数,因此类型声明实际上引入了一个新类型的整个系列-每个可能的参数值组合一个。 有许多语言支持某些版本的https://en.wikipedia.org/wiki/Generic_programming[泛型编程],其中可以指定操作它们的数据结构和算法,而不指定所涉及的确切类型。 例如,ML,Haskell,Ada,Eiffel,C中存在某种形式的泛型编程++,Java,C#,F#和Scala,仅举几例。 其中一些语言支持真正的参数多态性(例如ML,Haskell,Scala),而其他语言则支持临时的,基于模板的泛型编程风格(例如C++,Java)。 在各种语言中有这么多不同的泛型编程和参数类型,我们甚至不会尝试将Julia的参数类型与其他语言进行比较,而是专注于解释Julia的系统本身。 但是,我们会注意到,由于Julia是一种动态类型语言,不需要在编译时做出所有类型决策,因此可以相对轻松地处理静态参数类型系统中遇到的许多传统困
所有声明的类型( 数据类型 variety)可以参数化,在每种情况下具有相同的语法。 我们将按以下顺序讨论它们:首先是参数化复合类型,然后是参数化抽象类型,最后是参数化基元类型。
参数化复合类型
类型参数在类型名称之后立即引入,用花括号包围:
julia> struct Point{T}
x::T
y::T
end
此声明定义了一个新的参数类型, 点{T},持有两个类型的"坐标" T. 人们可能会问,是什么 T? 好吧,这正是参数类型的要点:它可以是任何类型(或者实际上是任何位类型的值,尽管在这里它被明确地用作类型)。 点{Float64} 是与替换定义的类型等效的具体类型 T 在定义 点 与 漂浮64. 因此,这个单一的声明实际上声明了无限数量的类型: 点{Float64}, 点{AbstractString}, 点{Int64} 等。 每个现在都是可用的混凝土类型:
julia> Point{Float64}
Point{Float64}
julia> Point{AbstractString}
Point{AbstractString}
类型 点{Float64} 是坐标为64位浮点值的点,而类型 点{AbstractString} 是一个"点",其"坐标"是字符串对象(参见 字符串)。
点 本身也是一个有效的类型对象,包含所有实例 点{Float64}, 点{AbstractString} 等。 作为亚型:
julia> Point{Float64} <: Point
true
julia> Point{AbstractString} <: Point
true
其他类型当然不是它的子类型:
julia> Float64 <: Point
false
julia> AbstractString <: Point
false
混凝土 点 具有不同值的类型 T 永远不会是彼此的子类型:
julia> Point{Float64} <: Point{Int64}
false
julia> Point{Float64} <: Point{Real}
false
|
警告这最后一点是_very_重要:即使 |
换句话说,在类型理论的说法中,Julia的类型参数是_invariant_,而不是被https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29[协变(甚至逆变)]。 这是出于实际原因:而任何实例 点{Float64} 可能在概念上是像一个实例 点{Real} 此外,这两种类型在内存中具有不同的表示形式:
*一个实例 点{Float64} 可以紧凑高效地表示为64位值的直接对;
*一个实例 点{Real} 必须能够容纳任何一对实例 真实的. 由于对象是 真实的 可以是任意大小和结构,在实践中是 点{Real} 必须表示为一对指向单独分配的指针 真实的 物体。
能够存储所获得的效率 点{Float64} 在数组的情况下,具有立即值的对象会被极大地放大: 数组{Float64} 可以存储为64位浮点值的连续内存块,而 数组{Real} 必须是指向单独分配的指针数组 真实的 对象—很可能是https://en.wikipedia.org/wiki/Object_type_%28object-oriented_programming%29#Boxing[盒装64位浮点值,但也可能是任意大的复杂对象,这些对象被声明为 真实的 抽象类型。
自 点{Float64} 不是 点{Real},下面的方法不能应用于类型的参数 点{Float64}:
function norm(p::Point{Real})
sqrt(p.x^2 + p.y^2)
end
定义接受所有类型参数的方法的正确方法 点{T} 哪里 T 是 真实的是:
function norm(p::Point{<:Real})
sqrt(p.x^2 + p.y^2)
end
(等同地,人们可以定义 函数范数(p::点{T} 其中T<:Real) 或 函数范数(p::点{T})其中T<:Real;见 UnionAll类型。)
更多的例子将在后面讨论 方法。
如何构造一个 点 对象? 可以为复合类型定义自定义构造函数,这将在 构造函数,但是在没有任何特殊构造函数声明的情况下,有两种默认的方法来创建新的复合对象,一种是显式给出类型参数,另一种是由对象构造函数的参数隐含的。
由于类型 点{Float64} 是混凝土类型相当于 点 声明与 漂浮64代替 T,它可以相应地应用为构造函数:
julia> p = Point{Float64}(1.0, 2.0)
Point{Float64}(1.0, 2.0)
julia> typeof(p)
Point{Float64}
对于默认构造函数,必须为每个字段提供一个参数:
julia> Point{Float64}(1.0)
ERROR: MethodError: no method matching Point{Float64}(::Float64)
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
[...]
julia> Point{Float64}(1.0, 2.0, 3.0)
ERROR: MethodError: no method matching Point{Float64}(::Float64, ::Float64, ::Float64)
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
[...]
仅为参数类型生成一个默认构造函数,因为无法重写它。 此构造函数接受任何参数并将其转换为字段类型。
在许多情况下,提供的类型是多余的 点 对象一想要构造,因为构造函数调用的参数类型已经隐式提供类型信息。 因此,你也可以申请 点 本身作为构造函数,前提是参数类型的隐含值 T 是明确的:
julia> p1 = Point(1.0,2.0)
Point{Float64}(1.0, 2.0)
朱莉娅>打字(p1)
点{Float64}
julia>p2=点(1,2)
点{Int64}(1, 2)
朱莉娅>打字(p2)
点{Int64}
的情况下 点,的类型 T 是明确暗示当且仅当两个参数 点 具有相同的类型。 如果不是这种情况,构造函数将以 方法;方法:
julia> Point(1,2.5)
ERROR: MethodError: no method matching Point(::Int64, ::Float64)
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.
Closest candidates are:
Point(::T, !Matched::T) where T
@ Main none:2
Stacktrace:
[...]
可以定义适当处理这种混合情况的构造函数方法,但这将在以后的文章中不讨论 构造函数。
参数化抽象类型
参数化抽象类型声明声明抽象类型的集合,方式大致相同:
julia> abstract type Pointy{T} end
有了这个宣言, 尖尖的{T} 是每个类型或整数值的不同抽象类型 T. 与参数复合类型一样,每个这样的实例都是 尖尖的:
julia> Pointy{Int64} <: Pointy
true
julia> Pointy{1} <: Pointy
true
参数抽象类型是不变的,就像参数复合类型一样:
julia> Pointy{Float64} <: Pointy{Real}
false
朱莉娅>尖尖的{Real} <:尖尖的{Float64}
错误
符号 尖尖的{<:Real} 可用于表达_covariant_类型的Julia类似物,而 尖尖的{>:Int} 类似于_contravariant_类型,但从技术上讲,这些表示类型的_sets_(请参阅 UnionAll类型)。
julia> Pointy{Float64} <: Pointy{<:Real}
true
julia> Pointy{Real} <: Pointy{>:Int}
true
就像普通的旧抽象类型用于在具体类型上创建有用的类型层次结构一样,参数化抽象类型对于参数化复合类型具有相同的目的。 例如,我们可以宣布 点{T} 是 尖尖的{T} 如下:
julia> struct Point{T} <: Pointy{T}
x::T
y::T
end
鉴于这样的声明,对于每个选择 T,我们有 点{T} 作为子类型的 尖尖的{T}:
julia> Point{Float64} <: Pointy{Float64}
true
julia> Point{Real} <: Pointy{Real}
true
julia> Point{AbstractString} <: Pointy{AbstractString}
true
这种关系也是不变的:
julia> Point{Float64} <: Pointy{Real}
false
julia> Point{Float64} <: Pointy{<:Real}
true
参数化抽象类型有什么用途 尖尖的 发球? 考虑如果我们创建一个只需要单个坐标的点状实现,因为点在对角线上_x=y_:
julia> struct DiagPoint{T} <: Pointy{T}
x::T
end
现在两者兼而有之 点{Float64} 和 图表点{Float64} 是 尖尖的{Float64} 抽象,并类似地为类型的每一个其他可能的选择 T. 这允许编程到所有共享的公共接口 尖尖的 对象,为两者实现 点 和 图示点. 然而,在我们在下一节介绍方法和调度之前,这是无法充分证明的, 方法。
在某些情况下,类型参数在所有可能的类型上自由范围可能没有意义。 在这种情况下,可以限制 T 像这样:
julia> abstract type Pointy{T<:Real} end
使用这样的声明,可以使用任何类型的子类型 真实的代替 T,但不是非子类型的类型 真实的:
julia> Pointy{Float64}
Pointy{Float64}
julia> Pointy{Real}
Pointy{Real}
julia> Pointy{AbstractString}
ERROR: TypeError: in Pointy, in T, expected T<:Real, got Type{AbstractString}
julia> Pointy{1}
ERROR: TypeError: in Pointy, in T, expected T<:Real, got a value of type Int64
可以以相同的方式限制参数化复合类型的类型参数:
struct Point{T<:Real} <: Pointy{T}
x::T
y::T
end
为了给出一个真实世界的例子,说明所有这些参数类型的机械是如何有用的,这里是Julia的实际定义 理性的不可变类型(除了我们为了简单起见省略了构造函数),表示整数的精确比例:
struct Rational{T<:Integer} <: Real
num::T
den::T
end
元组类型
元组是函数参数的抽象-没有函数本身。 函数参数的显着方面是它们的顺序和类型。 因此,元组类型类似于参数化的不可变类型,其中每个参数都是一个字段的类型。 例如,2元素元组类型类似于以下不可变类型:
struct Tuple2{A,B}
a::A
b::B
end
但是,有三个关键区别:
*元组类型可以有任意数量的参数。
*元组类型在其参数中为_covariant_: 元组{Int} 是 元组{Any}. 因此 元组{Any} 被认为是抽象类型,元组类型只有在它们的参数是具体的情况下才是具体的。
*元组没有字段名称;字段只能通过索引访问。
元组值用括号和逗号编写。 构造元组时,会按需生成适当的元组类型:
julia> typeof((1,"foo",2.5))
Tuple{Int64, String, Float64}
注意协方差的含义:
julia> Tuple{Int,AbstractString} <: Tuple{Real,Any}
true
julia> Tuple{Int,AbstractString} <: Tuple{Real,Real}
false
julia> Tuple{Int,AbstractString} <: Tuple{Real,}
false
直观地说,这对应于函数参数的类型是函数签名的子类型(当签名匹配时)。
Vararg元组类型
元组类型的最后一个参数可以是特殊值 瓦拉格,表示任意数量的尾随元素:
julia> mytupletype = Tuple{AbstractString,Vararg{Int}}
Tuple{AbstractString, Vararg{Int64}}
julia> isa(("1",), mytupletype)
true
julia> isa(("1",1), mytupletype)
true
julia> isa(("1",1,2), mytupletype)
true
julia> isa(("1",1,2,3.0), mytupletype)
false
此外 瓦拉格{T} 对应于零个或多个类型的元素 T. Vararg元组类型用于表示varargs方法接受的参数(参见 Varargs函数)。
特殊价值 瓦拉格{T,N} (当用作元组类型的最后一个参数时)对应于完全 N 类型的元素 T. N.婴儿{N,T} 是一个方便的别名 元组{Vararg{T,N}},即包含完全的元组类型 N 类型的元素 T.
命名元组类型
命名元组是 命名的,命名的类型,它有两个参数:一个给出字段名称的符号元组,一个给出字段类型的元组类型。 为方便起见, 命名的,命名的 类型使用打印 @NamedTuple宏它提供了一个方便 结构体-类似于通过以下方式声明这些类型的语法 关键::类型 声明,其中省略 ::类型 对应于 ::任何.
julia> typeof((a=1,b="hello")) # prints in macro form
@NamedTuple{a::Int64, b::String}
julia> NamedTuple{(:a, :b), Tuple{Int64, String}} # long form of the type
@NamedTuple{a::Int64, b::String}
该 开始。.. 结束 的形式 @NamedTuple 宏允许跨多行拆分声明(类似于结构声明),但在其他方面是等价的:
julia> @NamedTuple begin
a::Int
b::String
end
@NamedTuple{a::Int64, b::String}
A 命名的,命名的 type可以用作构造函数,接受单个元组参数。 建造的 命名的,命名的 type既可以是指定了两个参数的具体类型,也可以是仅指定字段名称的类型:
julia> @NamedTuple{a::Float32,b::String}((1, ""))
(a = 1.0f0, b = "")
julia> NamedTuple{(:a, :b)}((1, ""))
(a = 1, b = "")
如果指定了字段类型,则会转换参数。 否则直接使用参数的类型。
参数基元类型
基元类型也可以参数化声明。 例如,指针表示为原始类型,将在Julia中声明,如下所示:
# 32-bit system:
primitive type Ptr{T} 32 end
# 64-bit system:
primitive type Ptr{T} 64 end
与典型的参数复合类型相比,这些声明的稍微奇怪的特征是类型参数 T 在类型本身的定义中不使用-它只是一个抽象标记,本质上定义了具有相同结构的整个类型系列,仅通过其类型参数进行区分。 因此, Ptr{Float64} 和 Ptr{Int64} 是不同的类型,即使它们具有相同的表示。 当然,所有特定的指针类型都是伞的子类型 Ptr类型:
julia> Ptr{Float64} <: Ptr
true
julia> Ptr{Int64} <: Ptr
true
联合所有类型
我们已经说过,一个参数类型像 Ptr 作为其所有实例的超类型(Ptr{Int64} 等。). 这是如何工作的? Ptr 它本身不能是一个正常的数据类型,因为在不知道被引用数据的类型的情况下,该类型显然不能用于内存操作。 答案是 Ptr (或其他参数类型,如 阵列)是一种不同的类型,称为a 联合所有类型。 这样的类型表示某个参数的所有值的类型的_iterated union_。
联合所有 类型通常使用关键字编写 哪里. 例如 Ptr 可以更准确地写成 Ptr{T} 其中T,表示类型为 Ptr{T} 对于一些价值 T. 在此上下文中,参数 T 通常也被称为"类型变量",因为它就像一个在类型范围内的变量。 每个 哪里 引入单个类型变量,因此这些表达式对于具有多个参数的类型嵌套,例如 阵列{T,N} 哪里N哪里T.
类型应用程序语法 A{B,C} 需要 A 成为一个 联合所有 型,以及第一代 B 对于最外层的类型变量 A. 结果预计将是另一个 联合所有 类型,类型 C 然后被取代。 所以 A{B,C} 相当于 一个{B}{C}. 这就解释了为什么可以部分实例化类型,如 数组{Float64}:第一个参数值已被固定,但第二个参数仍复盖所有可能的值。 使用显式 哪里 语法,参数的任何子集都可以是固定的。 例如,所有1维数组的类型可以写成 阵列{T,1} 在哪里T.
类型变量可以使用子类型关系进行限制。 数组{T} 其中T<:整数 指元素类型为某种类型的所有数组 整数. 语法 阵列{<:Integer} 是一个方便的速记 数组{T} 其中T<:整数. 类型变量可以有下限和上限。 数组{T} 其中Int<:T<:Number 指的是所有数组 电话号码能够包含的 Int型s(自 T 必须至少和……一样大。 Int型). 语法 其中T>:Int 也可以仅指定类型变量的下限,并且 阵列{>:Int} 相当于 数组{T} 其中T>:Int.
自 哪里 表达式嵌套,类型变量边界可以引用外部类型变量。 例如 元组{T,Array{S}}其中S<:AbstractArray{T} 其中T<:Real 指第一个元素为some的2元组 真实的,并且其第二元素是 阵列 元素类型包含第一个元组元素类型的任何类型的数组。
该 哪里 关键字本身可以嵌套在更复杂的声明中。 例如,考虑以下声明创建的两种类型:
julia> const T1 = Array{Array{T, 1} where T, 1}
Vector{Vector} (alias for Array{Array{T, 1} where T, 1})
julia> const T2 = Array{Array{T, 1}, 1} where T
Array{Vector{T}, 1} where T
类型 T1 定义1维数组的1维数组;每个内部数组由相同类型的对象组成,但此类型可能因一个内部数组而异。 另一方面,类型 T2 定义一个一维数组的一维数组,所有这些数组的内部数组必须具有相同的类型。 请注意 T2 是抽象类型,例如, 阵列{Array{Int,1},1}<:T2,而 T1 是混凝土类型。 因此, T1 可以用零参数构造函数构造 a=T1() 但是 T2 不能。
有一个方便的语法来命名这样的类型,类似于函数定义语法的简短形式:
Vector{T} = Array{T, 1}
这相当于 const向量=数组{T,1} 在哪里T. 写作 向量{Float64} 相当于写作 阵列{Float64,1},以及伞型 向量资料 作为实例所有 阵列 对象,其中第二个参数-数组维度的数量-是1,而不管元素类型是什么。 在参数类型必须始终完整指定的语言中,这并不是特别有用,但在Julia中,这允许人们编写 向量资料 对于抽象类型,包括任何元素类型的所有一维密集数组。
单例类型
没有字段的不可变复合类型称为_singletons_。 形式上,如果
-
T是一个不可变的复合类型(即用结构体), -
a isa T&&b isa T暗示a===b,
然后 T 是单例类型。脚注:2[一些流行的语言有单例类型,包括Haskell,Scala和Ruby。] 基地。issingletontype缧可以用来检查一个类型是否是单例类型。 抽象类型不能通过构造成为单例类型。
从定义来看,这样的类型只能有一个实例:
julia> struct NoFields
end
julia> NoFields() === NoFields()
true
julia> Base.issingletontype(NoFields)
true
该 ===函数确认构造的 诺菲尔德 实际上是一样的。
当上述条件成立时,参数类型可以是单例类型。 例如,
julia> struct NoFieldsParam{T}
end
julia> Base.issingletontype(NoFieldsParam) # Can't be a singleton type ...
false
julia> NoFieldsParam{Int}() isa NoFieldsParam # ... because it has ...
true
julia> NoFieldsParam{Bool}() isa NoFieldsParam # ... multiple instances.
true
julia> Base.issingletontype(NoFieldsParam{Int}) # Parametrized, it is a singleton.
true
julia> NoFieldsParam{Int}() === NoFieldsParam{Int}()
true
函数类型
每个函数都有自己的类型,它是 功能.
julia> foo41(x) = x + 1
foo41 (generic function with 1 method)
julia> typeof(foo41)
typeof(foo41) (singleton type of function foo41, subtype of Function)
注意如何 字体(foo41) 打印本身。 这仅仅是打印的约定,因为它是一个可以像任何其他值一样使用的一流对象:
julia> T = typeof(foo41)
typeof(foo41) (singleton type of function foo41, subtype of Function)
julia> T <: Function
true
顶层定义的函数类型是单例函数。 必要时,您可以将它们与 ===.
闭包也有自己的类型,通常以以下结尾的名称打印 #<数字>. 在不同位置定义的函数的名称和类型是不同的,但不能保证在会话中以相同的方式打印。
julia> typeof(x -> x + 1)
var"#9#10"
闭包类型不一定是单例。
julia> addy(y) = x -> x + y
addy (generic function with 1 method)
julia> typeof(addy(1)) === typeof(addy(2))
true
julia> addy(1) === addy(2)
false
julia> Base.issingletontype(typeof(addy(1)))
false
类型{T} 类型选择器
对于每种类型 T, 类型{T} 是一个抽象参数类型,其唯一实例是对象 T. 直到我们讨论 参数化方法和 conversions,很难解释这个构造的效用,但简而言之,它允许人们将特定类型上的函数行为专业化为_values_。 这对于编写方法(特别是参数化方法)非常有用,这些方法的行为取决于作为显式参数给定的类型,而不是由其中一个参数的类型隐含的类型。
由于定义有点难以解析,我们来看一些例子:
julia> isa(Float64, Type{Float64})
true
julia> isa(Real, Type{Float64})
false
julia> isa(Real, Type{Real})
true
julia> isa(Float64, Type{Real})
false
换句话说, xref:base/base.adoc#Core.isa[isa(A,类型{B}) 当且仅当 A 和 B 是同一个对象,并且该对象是一个类型。
特别是,由于参数类型是 不变,我们有
julia> struct TypeParamExample{T}
x::T
end
julia> TypeParamExample isa Type{TypeParamExample}
true
julia> TypeParamExample{Int} isa Type{TypeParamExample}
false
julia> TypeParamExample{Int} isa Type{TypeParamExample{Int}}
true
没有参数, 类型 只是一个抽象类型,它具有所有类型对象作为其实例:
julia> isa(Type{Float64}, Type)
true
julia> isa(Float64, Type)
true
julia> isa(Real, Type)
true
任何不是类型的对象都不是 类型:
julia> isa(1, Type)
false
julia> isa("foo", Type)
false
而 类型 与任何其他抽象参数类型一样,它是Julia类型层次结构的一部分,除了某些特殊情况外,它不常用于方法签名之外。 另一个重要的用例 类型 是锐化字段类型,否则会被不太精确地捕获,例如 数据类型在下面的示例中,默认构造函数可能会导致依赖于精确包装类型的代码中的性能问题(类似于 抽象类型参数)。
julia> struct WrapType{T}
value::T
end
julia> WrapType(Float64) # default constructor, note DataType
WrapType{DataType}(Float64)
julia> WrapType(::Type{T}) where T = WrapType{Type{T}}(T)
WrapType
julia> WrapType(Float64) # sharpened constructor, note more precise Type{Float64}
WrapType{Type{Float64}}(Float64)
类型别名
# 32-bit system:
julia> UInt
UInt32
# 64-bit system:
julia> UInt
UInt64
这是通过以下代码完成的 基地/引导。jl:
if Int === Int64
const UInt = UInt64
else
const UInt = UInt32
end
(注意,不像 Int型, 浮子,浮子 不存在作为特定大小的类型别名 [医抽象浮]. 与整数寄存器不同,其中 Int型 反映该机器上本机指针的大小,浮点寄存器大小由IEEE-754标准指定。)
类型别名可以参数化:
julia> const Family{T} = Set{T}
Set
julia> Family{Char} === Set{Char}
true
类型的操作
由于Julia中的类型本身就是对象,所以普通函数可以对它们进行操作。 已经引入了一些对处理或探索类型特别有用的函数,例如 <: 运算符,其指示其左手操作数是否是其右手操作数的子类型。
该 伊萨函数测试对象是否为给定类型并返回true或false:
julia> isa(1, Int)
true
julia> isa(1, AbstractFloat)
false
该 类型;类型函数,已经在整个手册的例子中使用,返回其参数的类型。 因为,如上所述,类型是对象,它们也有类型,我们可以问它们的类型是什么:
julia> typeof(Rational{Int})
DataType
julia> typeof(Union{Real,String})
Union
如果我们重复这个过程呢? A类型的类型是什么? 碰巧,类型都是复合值,因此都具有 数据类型:
julia> typeof(DataType)
DataType
julia> typeof(Union)
DataType
数据类型 是它自己的类型。
适用于某些类型的另一个操作是 超类型,它揭示了一个类型的超类型。 仅声明类型(数据类型)有明确的超型:
julia> supertype(Float64)
AbstractFloat
julia>超类型(数字)
任何
julia>超类型(AbstractString)
任何
julia>超类型(任意)
任何
julia> supertype(Union{Float64,Int64})
ERROR: MethodError: no method matching supertype(::Type{Union{Float64, Int64}})
The function `supertype` exists, but no method is defined for this combination of argument types.
Closest candidates are:
[...]
定制漂亮印刷
通常,人们希望自定义类型实例的显示方式。 这是通过重载 展览功能。 例如,假设我们定义一个类型来表示极性形式的复数:
julia> struct Polar{T<:Real} <: Number
r::T
Θ::T
end
julia> Polar(r::Real,Θ::Real) = Polar(promote(r,Θ)...)
Polar
在这里,我们添加了一个自定义构造函数,以便它可以接受不同的参数 真实的类型并将其提升为通用类型(参见 构造函数和 转换和推广)。 (当然,我们也必须定义许多其他方法,以使其像 电话号码,例如 +, *, 一个, 零、推广规则等。)默认情况下,此类型的实例显示相当简单,包含有关类型名称和字段值的信息,例如 极地{Float64}(3.0,4.0).
如果我们希望它显示为 3.0*exp(4.0im),我们将定义以下方法将对象打印到给定的输出对象 伊俄 (表示文件,终端,缓冲区等;请参阅 网络和流):
julia> Base.show(io::IO, z::Polar) = print(io, z.r, " * exp(", z.Θ, "im)")
更细粒度的控制显示 极地 物是可能的。 特别是,有时人们既需要冗长的多行打印格式,用于在REPL和其他交互环境中显示单个对象,也需要更紧凑的单行格式。 印刷业或用于将对象显示为另一个对象的一部分(例如在数组中)。 虽然默认情况下 显示(io,z) 在这两种情况下都调用函数,您可以通过重载三参数形式来定义_different_多行格式来显示对象 展览 这需要 文本/纯文本 MIME类型作为其第二个参数(参见 多媒体I/O),例如:
julia> Base.show(io::IO, ::MIME"text/plain", z::Polar{T}) where{T} =
print(io, "Polar{$T} complex number:\n ", z)
(注意 打印(...,z) 这里将调用2参数 显示(io,z) 方法。)这导致:
julia> Polar(3, 4.0)
Polar{Float64} complex number:
3.0 * exp(4.0im)
julia> [Polar(3, 4.0), Polar(4.0,5.3)]
2-element Vector{Polar{Float64}}:
3.0 * exp(4.0im)
4.0 * exp(5.3im)
其中单行 显示(io,z) form仍然用于数组 极地 价值观。 从技术上讲,REPL调用 显示(z) 显示结果 z 执行一行,默认为 show(io,MIME("text/plain"),z) (哪里 伊俄 是一个 IOContext包装周围 标准输出),这反过来又默认为 显示(io,z),但你应该_not_定义新的 展览及展览方法,除非您正在定义新的多媒体显示处理程序(请参阅 多媒体I/O)。
而且,还可以定义 展览 其他MIME类型的方法,以便在支持此操作的环境(例如IJulia)中启用更丰富的对象显示(HTML,图像等)。 例如,我们可以定义格式化的HTML显示 极地 对象,带有上标和斜体,通过:
julia> Base.show(io::IO, ::MIME"text/html", z::Polar{T}) where {T} =
println(io, "<code>Polar{$T}</code> complex number: ",
z.r, " <i>e</i><sup>", z.Θ, " <i>i</i></sup>")
A 极地 然后,对象将在支持HTML显示的环境中使用HTML自动显示,但您可以调用 展览 手动获取HTML输出,如果你想:
julia> show(stdout, "text/html", Polar(3.0,4.0))
<code>Polar{Float64}</code> complex number: 3.0 <i>e</i><sup>4.0 <i>i</i></sup>
HTML渲染器将显示为: 极地{Float64} 复数:3.0_e_4.0_i_
根据经验,单行 展览 方法应该打印一个有效的Julia表达式来创建显示的对象。 当这 展览 方法包含中缀运算符,如乘法运算符(*)在我们的单行 展览 的方法 极地 上面,当作为另一个对象的一部分打印时,它可能无法正确解析。 要查看这一点,请考虑expression对象(请参阅 程序表示)这需要我们的一个特定实例的平方 极地 类型:
julia> a = Polar(3, 4.0)
Polar{Float64} complex number:
3.0 * exp(4.0im)
julia> print(:($a^2))
3.0 * exp(4.0im) ^ 2
因为运营商 ^ 优先级高于 * (见 运算符优先级和关联性),此输出不忠实地表示表达式 a^2 哪个应该等于 (3.0*exp(4.0im))^2. 要解决这个问题,我们必须为 基地。show_unquoted(io::IO,z::Polar,indent::Int,priority::Int),打印时由expression对象内部调用:
julia> function Base.show_unquoted(io::IO, z::Polar, ::Int, precedence::Int)
if Base.operator_precedence(:*) <= precedence
print(io, "(")
show(io, z)
print(io, ")")
else
show(io, z)
end
end
julia> :($a^2)
:((3.0 * exp(4.0im)) ^ 2)
上面定义的方法在调用周围添加括号 展览 当调用运算符的优先级高于或等于乘法的优先级时。 此检查允许在没有括号的情况下正确解析表达式(例如 :($a+2) 和 :($a==2))在打印时省略它们:
julia> :($a + 2)
:(3.0 * exp(4.0im) + 2)
julia>:($a==2)
:(3.0 * exp(4.0im) == 2)
在某些情况下,调整 展览 方法取决于上下文。 这可以通过 IOContext类型,它允许将上下文属性与包装的IO流一起传递。 例如,我们可以在我们的 展览 方法当 :紧凑型 属性设置为 真的,如果属性为 错误 或缺席:
julia> function Base.show(io::IO, z::Polar)
if get(io, :compact, false)::Bool
print(io, z.r, "ℯ", z.Θ, "im")
else
print(io, z.r, " * exp(", z.Θ, "im)")
end
end
当传递的IO流是一个 IOContext 反对 :紧凑型 属性集。 特别是,当打印具有多个列的数组时(其中水平空间有限)就是这种情况:
julia> show(IOContext(stdout, :compact=>true), Polar(3, 4.0))
3.0ℯ4.0im
julia> [Polar(3, 4.0) Polar(4.0,5.3)]
1×2 Matrix{Polar{Float64}}:
3.0ℯ4.0im 4.0ℯ5.3im
查看 IOContext可用于调整打印的常用属性列表的文档。
输出-功能摘要
以下是Julia中不同输出函数的简要总结以及它们之间的关系。 大多数新类型应该只需要定义 展览 方法,如果有的话。
* 显示(x)告诉当前环境显示 x 不管它认为什么最好。 (这甚至可能是像Jupyter或Pluto笔记本这样的图形显示。)默认情况下(例如在脚本中或在文本REPL中),它调用 show(io,"text/plain",x),或等同地 show(io,MIME"text/plain"(),x),为一个适当的 伊俄 溪流。 (在REPL, 伊俄 是一个 IOContext包装周围 标准输出.)REPL使用 展览及展览 输出计算表达式的结果。
*3参数 show(io,::MIME"text/plain",x)方法执行详细的漂亮-打印 x. 默认情况下(如果没有为 类型(x)),它调用2参数 显示(io,x). 它由2参数调用 repr("文本/普通",x). 其他3-参数 展览 如上所述,可以为其他MIME类型定义方法,以实现更丰富的 x 在一些交互式环境中。
*2参数 显示(io,x)是默认的简单文本表示 x. 它由1参数调用 代表(x),并且通常是您可能用于输入的格式 x 进入朱莉娅。 1-参数 显示(x) 电话 显示(stdout,x).
* 打印(io,x)默认调用 显示(io,x),但有几种类型有一个明显的 印刷业 格式-最值得注意的是,当 x 是字符串, 印刷业 输出原始文本,而 展览 输出用引号括起来的转义字符串。 1-论点 印刷(x) 电话 打印(stdout,x). 印刷业 也被称为 字符串(x). 请参阅 打印,打印(追加换行)和 [医印刷版](添加颜色等。),两者都调用 印刷业.
* 写(io,x),如果它被定义(它通常对新类型有_no_默认定义),写入一个"原始"二进制表示 x 到 伊俄,例如 x::Int32 将被写为4个字节。
熟悉可以附加到 伊俄 由一个 IOContext包装。 例如,REPL设置 :限制=>真 国旗来自 展览及展览 对于一个被评估的表达式,为了限制输出适合终端;你可以用 get(io,:limit,false). 并且当显示包含在例如多列矩阵内的对象时, :紧凑=>真 可以设置标志,您可以使用它进行查询 get(io,:compact,false).
"值类型"
在Julia中,您不能在_value_上发送,例如 真的 或 错误. 但是,您可以在参数类型上进行调度,并且Julia允许您包含"普通位"值(类型,符号,整数,浮点数,元组等)。)作为类型参数。 一个常见的例子是维数参数 阵列{T,N},在哪里 T 是一种类型(例如, 漂浮64)但是 N 只是一个 Int型.
您可以创建自己的自定义类型,将值作为参数,并使用它们来控制自定义类型的分派。 为了说明这个想法,让我们介绍参数类型 瓦尔{x},及其构造函数 Val(x)=Val{x}(),这是一种习惯性的方式来利用这种技术的情况下,你不需要一个更复杂的层次结构。
瓦尔被定义为:
julia> struct Val{x}
end
julia>Val(x)=Val{x}()
瓦尔
没有更多的实施 瓦尔 比这更好。 Julia标准库中的一些函数接受 瓦尔 实例作为参数,你也可以用它来编写自己的函数。 例如:
julia> firstlast(::Val{true}) = "First"
firstlast (generic function with 1 method)
julia> firstlast(::Val{false}) = "Last"
firstlast (generic function with 2 methods)
julia> firstlast(Val(true))
"First"
julia> firstlast(Val(false))
"Last"
为了整个Julia的一致性,调用站点应始终通过 瓦尔 instance_而不是使用_type,即使用 foo(Val(:bar)) 而不是 foo(Val{:bar}).
值得注意的是,错误使用参数化"值"类型非常容易,包括 瓦尔;在不利的情况下,你可以很容易地最终使你的代码的性能大大_worse_。 特别是,您永远不会想要编写如上所示的实际代码。 有关正确(和不当)使用的更多信息 瓦尔,请阅读 性能提示中更广泛的讨论。