1. 程式人生 > >第六章:julia互操作性和 超程式設計(learning julia譯)()

第六章: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{
            
           

相關推薦

julia操作性 程式設計(learning julia)

在本章中,我們將重點介紹Julia如何與外部世界互動,使用不同的方式對作業系統(OS)進行系統呼叫,或使用其他語言(如C和Python)的程式碼。 稍後,我們將以超程式設計的形式探索Julia的另一個重要方面。 我們還將研究Julia中預設提供的各種型別的巨集,

Taglib原理實現 標籤內常用方法總結

1。支援el表示式: import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager; private Object value = null; this.valu

阿里雲linux伺服器------域名購買、備案解析

當我們購買了伺服器後我們可以通過伺服器的公網ip直接訪問它,但在網際網路上你訪問別人的網站一般都是通過這個網站的網址(域名)來訪問它,而不是通過這個網站所在伺服器的ip地址。這樣做的好處:一是更加安全,不會暴露你伺服器的地址、二會起到一定的宣傳作用,因為我們花錢買個域名總會希望它有點特殊的含義。

玩轉資料結構——集合對映

集合(Set) 什麼是集合? 集合是承載元素的容器; 特點:每個元素只能存在一次 優點:去重 二分搜尋樹的新增操作add:不能盛放重複元素 是非常好的實現“集合”的底層資料結構 /** * 集合的介面 */ public interface Set<

翻譯libevent參考手冊bufferevent概念入門 (八)

bufferevent_setcb()函式修改bufferevent的一個或者多個回撥。readcb、writecb和eventcb函式將分別在已經讀取足夠的資料、已經寫入足夠的資料,或者發生錯誤時被呼叫。每個回撥函式的第一個引數都是發生了事件的bufferevent,最後一個引數都是呼叫buffereven

資料庫系統概念機械工業出版社,七版複習——資料庫設計E-R模型

E-R模型 實體-聯絡模型:Entity-Relationship Model E-R圖要點 實體(Entity) 客觀存在並可相互區分的事物叫實體(唯一標識)。 實體集(Entity Set) 是具有相同型別及共享相同性質(屬性)的實體集合。如全體學生。組成實體集的各實

2018-03-24 挖掘頻繁模式、關聯相關性基本概念

6.3 模式評估方法  大部分關聯規則挖掘演算法都使用支援度-置信度框架。儘管最小支援度和置信度閥值有助於排除大量無趣規則的探查,但仍然會產生一些使用者不感興趣的規則。強規則不一定是有趣的,甚至會誤導。    如:假設有10000個事務中,資料顯示6000個顧客事務包含計算機遊戲,7500個事務包含錄影,而4

EffectiveJava列舉註解

討論列舉和註解的最佳實踐。 30. 用enum代替int常量 列舉型別(enum type)是指由一組固定的常量組成合法值得型別,在程式語言還沒有引入列舉之前,表示列舉型別的常用模式是宣告一組具名的int常量,稱作int列舉模式。 int列舉模式的不

Flask 教程 個人主頁頭像

這是Flask Mega-Tutorial系列的第六部分,我將告訴你如何建立個人主頁。 本章將致力於為應用添加個人主頁。個人主頁用來展示使用者的相關資訊,其個人資訊由本人錄入。 我將為你展示如何動態地生成每個使用者的主頁,並提供一個編輯頁面給他們來更新個人

Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 實例化截頭錐體裁切

srv 參數 linear clam 階段 res lease log multiple 原文:Introduction to 3D Game Programming with DirectX 12 學習筆記之 --- 第十六章:實例化和截頭錐體裁切

異常機制

() 不同 finall try arr 運行時 運行 ror 則無 第六章:異常機制 異常的定義 異常:在程序運行過程中出現的意外事件,導致程序中斷執行。 異常處理 try...catch 語法:try{ //可能出現異常的代碼}catch(異常類型 異常對象名){

循環結構

結構 不執行 三種 表達式 成了 不改變 條件 運算符 步驟 第六章:循環結構(二) 一. for 循環 1.循環結構的四個組成部分 (1). 初始部分:設置循環的初始狀態,比如我們設置記錄循環次數的變量 i 為 0 . (2). 循環體:重復執行的代碼 .

需求評審如何進行

角色 來源 職責 介紹 技術 產品介紹 好的 通過 協調 前言今天我們講的需求評審包括兩個部分,需求過濾和需求評審。 需求過濾 1.需求分析不是所有需求都要做進產品,我們要根據公司和產品的定位,進行合適地分析和過濾。 我們需要分析出用戶需求所對應的本質,將其轉化為產品能夠提

linux用戶組權限管理

then login 通過 替換 ogr 特殊 conf 鎖定 全局 用戶組和權限管理 介紹安全3A ?資源分派: Authentication:認證 Authorization:授權 Accouting|Audition:審計 用戶user ?令牌token,identi

Node入門教程(8)path 模塊詳解

format QQ 調用 保留 微軟 posix interface join 結果 path 模塊詳解 path 模塊提供了一些工具函數,用於處理文件與目錄的路徑。由於windows和其他系統之間路徑不統一,path模塊還專門做了相關處理,屏蔽了彼此之間的差異。 可移

Docker | 構建私有倉庫

推送 sun 指定 公司 網絡環境 add 屬性 提示 回收機制 前言 上一章節,講解了利用Dockerfile和commit進行自定義鏡像的構建。大部分時候,公司運維或者實施部門在構建了符合公司業務的鏡像環境後,一般上不會上傳到公共資源庫的。這就需要自己搭建一個私有倉庫

編寫安全應用

利用 flash 網站 這一 ade 第六章 用戶數據 ack else 很多時候,安全應用是以犧牲復雜度(以及開發者的頭痛)為代價的。Tornado Web服務器從設計之初就在安全方面有了很多考慮,使其能夠更容易地防範那些常見的漏洞。安全cookies防止用戶的本地狀態被

《JAVA多線程編程核心技術》 筆記單例模式與多線程

會有 isp left sync con 多線程編程 鎖機制 數據 range 一、立即加載/"餓漢模式"和延遲加載/"懶漢模式" 立即加載(又稱餓漢模式):在使用類的時候已經將對象創建完畢,常見實現方法是直接new實例化 延遲加載(又稱懶漢模式):在調用get

隨機化

gin fat 偽隨機數發生器 偽隨機 運行 合成 內嵌 想要 ini 隨著設計變得越來越復雜,要想產生一個完整的激勵集來測試設計的功能也變得越來越困難。采用受約束的隨機測試法(CRT)自動產生測試集是目前的一種主要的方法。CRT由兩部分組成:使用隨機的數據流為DUT產生

隨機化續1

限制 調試 each 範圍 實例 func 文件中 約束 hand 6.6 pre_randomize和post_randomize函數 我們在調用randomize()函數之前或者之後要立即執行一些操作。比如,在隨機化之前可能要設置類裏的一些非隨機變量(上下限、權重),或