第六章:julia互操作性和 元编程(learning julia译)()

在本章中,我们将重点介绍Julia如何与外部世界交互,使用不同的方式对操作系统(OS)进行系统调用,或使用其他语言(如C和Python)的代码。 稍后,我们将以元编程的形式探索Julia的另一个重要方面。 我们还将研究Julia中默认提供的各种类型的宏,以及如何在需要时创建一个宏。 最后,我们将尝试理解Julia的不同反射特性。

以下是我们将在本章中介绍的主题列表:

  • 与操作系统交互
  • 调用C和Python
  • 表达式和宏
  • 内置宏
  • 键入内省和反射功能

与操作系统交互

本章略
Julia的最佳功能之一是它的优秀REPL,它在调用特定于操作系统的命令时为用户提供了很大的灵活性。 对于本书,我一直在使用基于Linux的操作系统,因此,使用的所有命令都将完全基于Linux。 对于使用Windows的用户,系统命令将与底层操作系统不同并且是本机的。

I / O操作

在阅读了一些文件系统操作之后,让我们继续讨论I / O和网络相关的任务。 在这里,我们将执行一些最常用函数的列表,同时执行前面提到的操作。

STDOUT,STDERR和STDIN是Julia中的三个全局变量,分别表示标准输出,错误和输入流

  • open()函数:该函数用于打开文件以读取或写入文件。一个非常简单的用例是在当前目录中创建一个名为sample.txt的文件,并打开Julia REPL:
julia> file = open("sample.txt")
IOStream(<file sample.txt>)
julia> file
IOStream(<file sample.txt>)

现在,由于文件已打开,我们该如何处理呢? 我们可能会继续阅读它的内容。 让我们假设我们有你好世界! 写在sample.txt文件中; 当我们现在尝试阅读其内容时会发生什么? 看一下下面的代码:

julia> lines = readlines(file)
1-element Array{String,1}:
"hello world!!\n"

readlines()函数将专门用于读取此文件的所有内容。 结果以字符串数组形式出现。 但是,在Julia中打开文件的方法不止一种,这取决于我们打开文件的模式。 常见的是r,w和r +,其中r +代表读取和写入。

  • 写入和读取功能:顾名思义,该功能分别用于向文件写入内容和从文件读取内容。 此处显示的快速示例将帮助您基本了解这两个功能:
# open up a file named "sample.txt" and write the message
julia> write("sample.txt", "hi how are you doing?")
21
julia> read("sample.txt")
21-element Array{UInt8,1}:
0x68
0x69
0x20
0x68
0x6f
0x77
0x20
0x61
0x72
0x65
0x20
0x79
0x6f
0x75
0x20
0x64
0x6f
0x69
0x6e
0x67
0x3f
# to actually read the contents
julia> readline("sample.txt")
"hi how are you doing?"

这里,首先,我们调用write函数打开一个文件sample.txt,然后写出内容“你好,你好吗?”。 接下来,我们现在使用read函数打开同一个文件并尝试读取内容。 但结果并不是我们所期望的!

相反,我们得到一个Array {UInt8,1}类型的数组,它表明我们正在尝试访问无符号整数流。 仔细研究这个函数的方法可以进一步消除混淆:

julia> methods(read)
# 37 methods for generic function "read":
read(::Base.DevNullStream, ::Type{UInt8}) at coreio.jl:13
read(s::IOStream) at iostream.jl:236
read(s::IOStream, ::Type{UInt8}) at iostream.jl:151
read(s::IOStream,
T::Union{Type{Int16},Type{Int32},Type{Int64},Type{UInt16},Type{
UInt32},Type{UInt64}}) at iostream.jl:160
read(s::IOStream, ::Type{Char}) at iostream.jl:180
read(s::IOStream, nb::Integer; all) at iostream.jl:260
...
..
.

要在读取后关闭文件,我们有一个名为close()的函数,它接受文件名作为参数:

julia> close("sample.txt")

例子

现在我们已经阅读了有关文件系统和I / O操作的内容,现在我们可以给出一个完整的示例,说明如何创建一个简单的Julia脚本来读取和写入数据到文件中。

在这里,我们有一个名为sample.jl的文件,我们用以下方式创建它:

# Arguments
in_file = ARGS[1]
out_file = ARGS[2]
# Keeping track using a counter
counter = 0
for line in eachline(in_file)
for word in split(line)
if word == "Julia"
counter += 1
end
end
end
# write the contents to the output file
write(out_file, "the count for julia is $counter")
# read the contents fom the o/p file for user's help
for line in readlines(out_file)
println(line)
end

这个脚本基本上有两个输入:

  • in_file,这是一个TXT文件,我们将从中读取数据
  • out_file,我们将写入结果

我们要提供的in_file的名称为readme.txt,并包含
以下信息(以下文字摘自Julia官方网站):

mylinux-machine:home myuser$ cat readme.txt

Julia is a high-level, high-performance dynamic programming language for numerical
computing. It provides a sophisticated compiler, distributed parallel execution, numerical
accuracy, and an extensive mathematical function library. Julia’s Base library, largely
written in Julia itself, also integrates mature, best-of-breed open source C and Fortran
libraries for linear algebra, random number generation, signal processing, and string
processing. In addition, the Julia developer community is contributing a number of
external packages through Julia’s built-in package manager at a rapid pace. IJulia, a
collaboration between the Jupyter and Julia communities, provides a powerful browserbased graphical notebook interface to Julia.

虽然out_file将是我们写入的文件,但在本例中,我们有result.out。 现在我们继续从命令提示符以下列方式调用脚本:

mylinux-machine:home myuser$ julia sample.jl readme.txt result.out
the count for julia is 4

如果仔细检查脚本sample.jl,我们只是读入一个我们作为参数传递的文件(在本例中为readme.txt),并计算Julia在文本中出现的确切单词的次数。 最后,将总字数写入文件result.out:

mylinux-machine:home myuser$ cat result.out
the count for julia is 4

调用C和Python

正如我们从第一次介绍中所知道的那样,朱莉娅从两个世界中都取得了最好的成绩。 它在易于代码和维护方面与Python相匹配,同时它的目标是实现C的速度。

但是如果我们真的需要外部调用用这两种语言编写的代码或函数呢? 然后,我们需要能够将代码直接导入Julia,并能够使用它。 让我们一个接一个地看看Julia如何设法对这两种编程语言进行外部调用。

调用C

C可以被称为现代编程语言的母亲,而今天的大多数语言都在其源代码的某处使用C来使代码运行得更快,或者只是在现有的C函数或库上添加包装器。

Julia还在其一些库中使用了C语言,尽管大多数核心库都是用Julia本身编写的。 有些事情需要Julia调用C。他们如下:

  • 很方便的调用
  • 绝对没有开销
  • 在调用C函数之前不需要进一步处理或编译,因此可以直接使用

在我们继续实际开始从Julia调用C之前,让我们看一下编译器(如LLVM)在调用任何C函数之前需要知道的内容。

  • 库的名称
  • 函数本身的名称
  • 参数的数量和类型(也称为Arity)
  • 返回函数的类型
  • 传递的参数值

在Julia中执行此操作的功能称为ccall()。 它的语法可以写成如下:

ccall((:name,"lib"), return_type, (arg1_type, arg2_type...), arg1, arg2)

以下是如何使用ccall调用C函数的简单示例。 这里我们从标准C库本身调用时钟函数,它将返回一个Int64类型值的值:

julia> ccall((:clock, :libc), Int64, ())
1437953

在这里我们也可以这样做:

julia> ccall((:clock, :libc), Cint, ())
1467922

请注意,我们能够用Cint替换Int64。 但是什么是Cint? 如果打开REPL,这里的Cint实际上是signed int c-type的C等价物:

julia> Int32 == Cint
true

那令人惊讶吗? 没有! 原因是Julia将这些别名用于基于C的类型。 他们定义了更多,如Cint,Cuint,Clong,Culong和Cchar。 让我们尝试另一个例子,但是复杂的例子:

julia> syspath = ccall( (:getenv, :libc), Ptr{Cchar}, (Ptr{Cchar},),
"SHELL")
Ptr{Int8} @0x00007fff5ca5bbe0
julia> unsafe_string(syspath)
"/bin/bash"

这里我们调用标准C库来使用getenv函数并从中获取SHELL值,它本身就是一个Julia字符串。

但是,这里我们传递的是Ptr {Cchar}类型的参数,结果的期望参数类型也是Ptr {Cchar},它们都代表Julia类型(指向字符的指针)。 因此,我们试图获得的总体结果是来自环境变量的SHELL参数的值。

但这次执行是如何发生的呢? 在调用ccall之前,有一个电话
在内部转换函数,将SHELL(这是一个Julia字符串)转换为
一个Ptr {Cchar}类型。

在此之后,发生实际调用,返回Ptr {Int8} @ 0x00007fff5ca5bbe0值,其中存在指向Int8的指针以及缓冲区地址。 为了使其更具可读性,我们使用名称为unsafe_string的函数将其转换为Julia String类型。

此函数以unsafe为前缀的原因是,如果指针的值不是有效的内存地址,它将崩溃。 这通常被称为分段错误。 但是,这里它像往常一样工作,因为Ptr {Int8} @ 0x00007fff5ca5bbe0是一个有效的内存地址。

我们能够将值作为Julia类型获取的原因是因为在调用基于C的字符串和整数时,有许多转换定义为标准。 甚至可以使用复合类型将C结构复制到Julia。

表达式和宏

元编程很有趣,与一些竞争对手相比,Julia肯定拥有最好的元编程功能之一。 初始线索和灵感已从Lisp中取出,并同样地Lisp中,朱写入朱莉娅本身-或者换言之,Julia是homoiconic。

为了解释Julia如何解释代码,这里有一个小代码片段:

julia> code = "println(\"hello world \")"
"println(\"hello world \")"
julia> expression = parse(code)
:(println("hello world "))
julia> typeof(expression)
Expr

在这里,我们简单地将一个普通的Julia代码println(“hello world”)作为字符串传递给函数解析。 反过来,这个函数接受这段字符串并将其转换为一个名为Expr的数据类型,这在前面的代码中很明显。

为了更密切地了解这个Expr类型的数据,我们可以深入挖掘:

julia> fieldnames(expression)
3-element Array{Symbol,1}:
:head
:args
:typ
julia> expression.args
2-element Array{Any,1}:
:println
"hello world "
julia> expression.head
:call
julia> expression.typ
Any

任何Expr类型的对象都有三个部分:

  • 符号,在这种情况下是:call和:println
  • 一系列参数,在这种情况下,是:println和“hello world”
  • 最后,结果类型,这里是Any

Julia还提供了另一个名为dump的函数,它有助于提供有关表达式类型对象的详细信息:

julia> dump(expression)
Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol println
2: String "hello world "
type: Any

但是,我们知道什么是参数和返回类型。 有一件事我们没有很好的理解是符号(Symbol )!

Julia中的符号与Lisp,Scheme或Ruby中的符号相同。 当一种语言可以表示自己的代码时,它需要一种方式来表示分配,函数调用,可以写成文字值的东西。 它还需要一种表示自己变量的方法。 也就是说,您需要一种方法将其表示为数据。

我们举个例子:

julia> foo == "foo"

符号和字符串之间的差异是该比较左侧的foo与右侧的“foo”之间的差异。 在左侧,foo是一个标识符,它计算当前范围内绑定到变量foo的值。 在右边,“foo”是一个字符串文字,它的结果是字符串值“foo”。 Lisp和Julia中的符号表示如何将变量表示为数据。 字符串只代表自己。

还有一种创建表达式的方法,即使用Expr构造函数。 这里给出了一个可以创建的最基本的表达式:

julia> sample_expr = Expr(:call, +, 10, 20)
:((+)(10,20))
julia> eval(sample_expr)
30

那么,这里发生了什么? 我们创建了一个自定义表达式,其中我们传递:call作为第一个参数,它表示表达式的头部。 接下来,我们提供+,10和20作为此表达式的参数。 要在运行时最终评估此表达式,我们使用名为eval的函数:

julia> sample_expr.args
3-element Array{Any,1}:
+
10
20
julia> eval(sample_expr)
30

请注意,eval将Expr -type值作为输入,这就是sample_expr的数据类型为Expr的原因。

现在,假设我们想代替计算该表达式:

julia> sample_expr = Expr(:call, +, x, y)
ERROR: UndefVarError: x not defined

这会抛出一个错误,说没有定义x,实际情况也是如此。 但是我们该怎么做呢? 我们如何用我们想要传递的值替换这些值? 或者,换句话说,我们如何插入这些变量?

一种方法是直接在我们尝试创建的表达式中注入x和y的值,然后进行求值:

julia> x = 10
10
julia> y = 10
10
julia> sample_expr = Expr(:call, +, :x, :y)
:((+)(10,10))
# or even this works
julia> sample_expr = Expr(:call, +, x, y)
:((+)(10,10))
julia> eval(sample_expr)
20

实现相同结果的另一种方法是使用$,但是,在这种情况下,我们不会在运行时插值; 相反,我们将在解析表达式时执行此操作。 请参阅以下示例:

julia> x = 10
10
julia> y = 10
10
julia> e = :($x + $y)
:(10 + 10)
julia> eval(e)
20

因此,总体上有两种方法可以使用Expr对象进行插值:

  • 在运行时使用引号(:)
  • 在解析时使用美元($)

表达表达式的另一种方法是使用quote关键字。 在大多数情况下,使用带引号的表达式与我们目前使用的表达式是同义的 - 例如,表达式前面带有“:“

julia> quote
30 * 100
end
quote # REPL[12], line 2:
30 * 100
end
julia> eval(ans)
3000
julia> :(30 * 100)
:(30 * 100)
julia> eval(ans)
3000

您可能已经注意到,两者之间没有区别! 但是,为什么qtuote可以单独提供给最终用户? 正如官方Julia文档所述,主要原因是因为在使用quote时,此表单将QuoteNodes元素引入表达式树,在操作树时必须考虑这些元素。

换句话说,使用引用,您可以更好地拼接实现的内容。

Julia中的宏是一个非常强大的代码求值工具,在前面的一些章节中,我们一直在使用它们(例如,使用@time来了解程序的整体计算时间)。

宏类似于函数,但是函数将普通变量作为参数,宏,另一方面,取表达式并返回修改后的表达式。

函数在运行时进行计算。 在分析时评估宏。 这意味着宏可以在执行之前操作函数(和其他代码)。

宏的语法可以定义如下:

macro NAME
# some custom code
# return modified expression
end

在调用宏时,使用@符号,用于表示Julia中的宏。 但是,此符号类似于其他语言中使用的符号,例如Python中的装饰器:

julia> macro HELLO(name)
:( println("Hello! ", $name))
end
@HELLO (macro with 1 method)
julia> @HELLO("Raaaul")
Hello! Raaaul

要查看宏内部的内容并帮助更好地调试它们,我们可以使用名为macroexpand的Julia函数:

julia> macroexpand(:(@HELLO("rahul")))
:(println("Hello!","rahul"))

但为什么要进行元编程呢?

要了解使用元编程的原因,请查看此方案。 你有一个类似于这里给出的函数,它接受一个数字并打印给定的次数:

julia> function foo(n::Int64)
for i=1:n
println("foo")
end
end
foo (generic function with 1 method)
julia> foo(2)
foo
foo

简单? 是。 但是现在假设,在代码中的某个不同模块中,您正在执行相同类型的操作,但使用其他名称:

julia> function bar(n::Int64)
for i=1:n
println("bar")
end
end
bar (generic function with 1 method)
julia> bar(2)
bar
bar
julia> function baz(n::Int64)
for i=1:n
println("baz")
end
end
baz (generic function with 1 method)
julia> baz(2)
baz
baz

很多代码重复,对吗? 你当然希望避免这个问题,这就是元编程来拯救的地方。 使用元编程,您基本上可以动态生成代码,这可以减少您作为开发人员的时间。

所以,为了确保我们不重复自己,让我们看看我们能做些什么:

julia> for sym in [:foo, :bar, :baz]
@eval function $(Symbol(string(sym)))(n::Int64)
for i in 1:n
println("$sym")
end
end
end
julia> foo(1)
foo
julia> bar(1)
bar
julia> baz(1)
baz

正如您所看到的,我们能够在元程序的帮助下做同样的事情! 在下一节中,我们将讨论Julia中可用的一些非常重要的内置宏!

内置宏

这里给出了Julia中可用宏的列表:

@MIME_str       @code_typed          @fetch             @less       @schedule       @timed
@__FILE__       @code_warntype       @fetchfrom         @linux      @show           @timev
@allocated      @deprecate           @functionloc       @linux_only @simd
@uint128_str
@assert         @doc                 @generated         @noinline   @spawn @unix
@async @doc_str @gensym @osx @spawnat
@unix_only
@b_str @edit    @goto                @osx_only          @sprintf    @v_str
@big_str        @elapsed @html_str   @parallel          @static
@vectorize_1arg
@boundscheck    @enum                @inbounds          @polly      @sync
@vectorize_2arg
@cmd @eval      @inline             @printf             @task       @view
@code_llvm      @evalpoly           @int128_str         @profile    @text_str       @which
@code_lowered   @everywhere         @ip_str             @r_str      @threadcall
@windows
@code_native    @fastmath           @label              @s_str      @time
@windows_only

我们将重点讨论一些最常用的问题。 开始吧探索他们:

  • @time:这个是一个有用的宏,用于查找程序完成所需的总时间。 换句话说,我们可以用它来跟踪程序的执行速度。
    它的用法以及一个小例子在这里给出:
# simple function to find recursive sum
julia> function recursive_sum(n)
if n == 0
return 0
else
return n + recursive_sum(n-1)
end
end
recursive_sum (generic function with 1 method)
# A bit slow to run for the 1st Time, as the function gets
compiled.
julia> @time recursive_sum(10000)
0.003905 seconds (450 allocations: 25.816 KiB)
50005000
# Much much faster in the second run!
julia> @time recursive_sum(10000)
0.000071 seconds (5 allocations: 176 bytes)
50005000

Julia擅长科学计算,在了解程序运行所花费的时间后,@ time会派上用场。

  • @elapsed:与@time宏非常相似的是@elapsed宏,它会丢弃结果,只显示程序运行所花费的时间。
    将@elapsed重新应用于之前的平均函数:
julia> @elapsed average(10000000, 1000000000)
2.144e-6
julia> typeof(@elapsed average(10000000, 1000000000))
Float64

@elapsed的结果始终以浮点数表示。

  • @show:这个宏与任何一段代码一起使用时,将返回一个表达式并计算它的结果。 这里给出的示例将有助于使用@show:
julia> @show(println("hello world"))
hello world
println("hello world") = nothing
julia> @show(:(println("hello world")))
$(Expr(:quote, :(println("hello world")))) = :(println("hello
world"))
:(println("hello world"))
julia> @show(:(3*2))
$(Expr(:quote, :(3 * 2))) = :(3 * 2)
:(3 * 2)
julia> @show(3*2)
3 * 2 = 6
6
julia> @show(Int64)
Int64 = Int64
Int64
  • @which:当您为单个函数提供多个方法并且想要检查或了解在提供特定参数集时将调用的方法时,此宏非常有用。
    因为Julia在很大程度上依赖于多次调度,所以@which宏进来了方便很多地方。 以下是展示其用法的示例:
# create a function that tripples an Integer
julia> function tripple(n::Int64)
3n
end
tripple (generic function with 1 method)
# redefine the same function to accept Float
julia> function tripple(n::Float64)
3n
end
tripple (generic function with 2 methods)
# check the methods available for this function
julia> methods(tripple)
# 2 methods for generic function "tripple":
tripple(n::Float64) in Main at REPL[22]:2
tripple(n::Int64) in Main at REPL[21]:2
# Get the correct method , when 'n' is an Int64
julia> @which tripple(10)
tripple(n::Int64) in Main at REPL[21]:2
# Get the correct method , when 'n' is Float64
julia> @which tripple(10.0)
tripple(n::Float64) in Main at REPL[22]:2

因此,正如您所看到的,对于给定的一组参数,@ which能够告诉函数的正确方法。

  • @task:Julia中的任务类似于协程。 此宏可用于在不运行任务的情况下返回任务,因此可以稍后运行。
    我们现在将尝试创建一个非常简单的任务,并展示如何使用@task在以后运行它 时间:
julia> say_hello() = println("hello world")
say_hello (generic function with 1 method)
julia> say_hello_task = @task say_hello()
Task (runnable) @0x000000010dcdfa90
julia> istaskstarted(say_hello_task)
false
julia> schedule(say_hello_task)
hello world
Task (queued) @0x000000010dcdfa90
julia> yield()
julia> istaskdone(say_hello_task)
true
  • @code_llvm,@ code_lowered,@ code_typed,@
    code_native和@code_warntype:这些宏都与代码在Julia中表示的方式有关,以及它如何与它下面的层交互。
    它基本上挖掘了一个额外的层,并帮助您调试并了解幕后发生的事情。 我们来看一个斐波纳契数列的简单例子:
julia> function fibonacci(n::Int64)
if n < 2
n
else
fibonacci(n-1) + fibonacci(n-2)
end
end
fibonacci (generic function with 1 method)
# OR, can also define it this way
julia> fibonacci(n::Int64) = n < 2 ? n : fibonacci(n-1) +
fibonacci(n-2)
fibonacci (generic function with 1 method)
julia> fibonacci(10)
55

现在让我们逐个尝试这段代码中的每一个宏:

@code_lowered以一种格式显示代码,该格式旨在供编译器进一步执行。 此格式主要是内部格式,不适合人类使用。 代码转换为单个静态赋值,其中每个变量只分配一次,并定义每个变量在使用之前:

julia> @code_lowered fibonacci(10)
CodeInfo(:(begin
nothing
unless n < 2 goto 4
return n
4:
return (Main.fibonacci)(n - 1) + (Main.fibonacci)(n - 2)
end))

@code_typed表示类型推断和局内后特定参数类型集的方法实现:

julia> @code_typed fibonacci(10)
CodeInfo(:(begin
unless (Base.slt_int)(n, 2)::Bool goto 3
return n
3:
SSAValue(1) = $(Expr(:invoke, MethodInstance for
fibonacci(::Int64), :(Main.fibonacci), :((Base.sub_int)(n,
1)::Int64)))
SSAValue(0) = $(Expr(:invoke, MethodInstance for
fibonacci(::Int64), :(Main.fibonacci), :((Base.sub_int)(n,
2)::Int64)))
return (Base.add_int)(SSAValue(1), SSAValue(0))::Int64
end))=>Int64
julia> @code_warntype fibonacci(10)
Variables:
#self#::#fibonacci
n::Int64
Body:
begin
unless (Base.slt_int)(n::Int64, 2)::Bool goto 3
return n::Int64
3:
SSAValue(1) = $(Expr(:invoke, MethodInstance for
fibonacci(::Int64), :(Main.fibonacci), :((Base.sub_int)(n,
1)::Int64)))
SSAValue(0) = $(Expr(:invoke, MethodInstance for
fibonacci(::Int64), :(Main.fibonacci), :((Base.sub_int)(n,
2)::Int64)))
return (Base.add_int)(SSAValue(1), SSAValue(0))::Int64
end::Int64

Julia使用LLVM编译器框架生成机器代码。 它使用LLVM的C ++ API来构造此LLVM中间表示。 因此,当我们执行@code_llvm时,它生成的代码只是中间表示以及一些高级优化:

julia> @code_llvm fibonacci(10)
define i64 @julia_fibonacci_61143.2(i64) #0 !dbg !5 {
top:
%1 = icmp sgt i64 %0, 1
br i1 %1, label %L3, label %if
if: ; preds =
%top
ret i64 %0
L3: ; preds =
%top
%2 = add i64 %0, -1
%3 = call i64 @julia_fibonacci_61143(i64 %2)
%4 = add i64 %0, -2
%5 = call i64 @julia_fibonacci_61143(i64 %4)
%6 = add i64 %5, %3
ret i64 %6
}

Julia使用并执行本机代码。 @code_native恰好代表了它,它只是内存中的二进制代码。 这个类似于汇编语言,足够代表说明:

julia> @code_native fibonacci(10)
.section __TEXT,__text,regular,pure_instructions
Filename: REPL[50]
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %rbx
pushq %rax
movq %rdi, %rbx
Source line: 1
cmpq $1, %rbx
jle L63
leaq -1(%rbx), %rdi
movabsq $fibonacci, %r15
callq *%r15
movq %rax, %r14
addq $-2, %rbx
movq %rbx, %rdi
callq *%r15
addq %r14, %rax
addq $8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
L63:
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
nopl (%rax)

键入内省和反射功能

类型内省和反射功能是任何现代编程语言中非常有用的部分。 他们的需求源于这样一个事实,即编码时很多时候,我们遇到了需要理解我们正在处理的对象或数据类型的情况。 有时,我们可能需要找到一个对象的类型,有时我们可能最终根据该对象的类型和属性编写一些逻辑。

内省类型

正如我们从起始章节所知,Julia支持多个调度,我们可以从任何抽象数据类型创建一个新的数据类型,让我们定义一个名为Student的新类型,然后为这种类型创建两个样本对象:

julia> type Student
name::String
age::Int64
end
julia> alpha = Student("alpha",24)
Student("alpha", 24)
julia> beta = Student("beta",25)
Student("beta", 25)

很简单! 现在我们有两个这样的学生,名字是alpha和beta,我怎么能确定它们是哪种类型? 你应该考虑我们之前研究的一个功能,还记得吗? 如果没有,那么让我们看一下答案的以下示例:

julia> typeof(alpha)
Student
julia> typeof(beta)
Student

啊,功能类型就是那个。 但是如果我们想要使用单个函数检查对象是否属于给定类型呢? 我们来看看下面的代码:

# similar to isinstance in python
julia> isa(alpha, Student)
true
# even this is possible!
julia> alpha isa Student
true

如果查看第二个实现,即alpha是Student,您可以看到正在使用的内联语法。 最终用户阅读和理解的难易程度如何? 由于这个原因和许多原因,Julia是一个很好的阅读语言。

反射功能

Reflection实际上为您提供了在运行时操作对象属性的能力。 因此,每当我们在Julia中创建一个函数时,我们都可以询问一些关于它的基本内容,例如该函数有多少个参数,或者更可能的是,当前作用域中可用的函数的方法是什么,等等。

要更好地理解,请查看此代码段,该代码段创建一个函数,然后尝试询问有关其属性的一些问题:

# the first method tries to take in all integer values
julia> function calculate_quad(a::Int64,b::Int64,c::Int64,x::Int64)
return a*x^2 + b*x + c
end
calculate_quad (generic function with 2 methods)
julia> calculate_quad(1,2,3,4)
27
# the second method takes all but x as integer values
julia> function calculate_quad(a::Int64,b::Int64,c::Int64,x::Float64)
return a*x^2 + b*x + c
end
calculate_quad (generic function with 3 methods)
julia> calculate_quad(1,2,3,4.75)
35.0625
# to know what all methods does the function supports
# which as we can see that there are 2 currently
julia> methods(calculate_quad)
# 3 methods for generic function "calculate_quad":
calculate_quad(a::Int64, b::Int64, c::Int64, x::Float64) in Main at
REPL[31]:2
calculate_quad(a::Int64, b::Int64, c::Int64, x::Int64) in Main at
REPL[29]:2
calculate_quad(a, b, c, x) in Main at REPL[27]:2

这是一个如何知道函数的方法签名的演示。 接下来是如何知道类型中的所有字段。 为此,我们使用一个名为fieldnames的函数,它给出了在类型内声明的字段的所有名称:

# from the already declared Student class
julia> fieldnames(Student)
2-element Array{Symbol,1}:
:name
:age

另一方面,如果您想知道类型中所有字段的各个字段类型,系统可能会提示您使用类型的types属性,这会导致SimpleVector保存所有数据类型:

julia> Student.types
svec(String, Int64)
julia> typeof(Student.types)
SimpleVector

反射被广泛应用于元编程,和Julia,是homoiconic,使用此属性的优势。 在上一个主题中,当我们介绍内置宏时,我们谈了很多关于Julia如何读取自己的代码,以及一些宏(@code_llvm,@ code_lowered,@ code_native,@ code_typed和@code_warntype)如何分解 代码分为不同的可读格式。 我们强烈建议您回到上一个主题并为自己试一试。

概要

在本章中,我们学习了如何在Julia中运行C代码和Python代码。 我们还继续研究如何与操作系统内部交互,包括文件系统和I / O相关任务。 然后我们继续学习和探索元编程的世界,在那里我们详细了解了表达式是什么,以及如何创建宏,以及使用已经提供的内置宏。
在下一章中,我们将使用迄今为止所获得的所有知识来应用我们对Julia语言的学习,并且我们将共同努力,以便在数学和统计目的方面充分利用它。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值