[英]type of function in Julia
考慮以下代碼:
julia> function foo(x::Float64)::Float64
return 2x
end
foo (generic function with 1 method)
julia> typeof(foo)
typeof(foo)
typeof(foo)
不返回更有意義的東西肯定是有原因的,例如(Float64 -> Float64)
。 它是什么?
我在看Zygote
代碼時遇到了這個。
當您編寫typeof(foo)
您是在詢問函數foo
的類型。 這個函數可以有多個方法(類型穩定與否——但這是另一個問題),這些方法有不同的簽名(參數類型),對於其中一些方法,編譯器可能能夠推斷出返回類型,而對於其他方法它可能不會(並且您不應該依賴它 AFAICT --- 只是假設大部分時間編譯器都在做正確的工作)。
舉個例子,考慮一下這段代碼(1個函數,2個方法):
julia> f1(::Int) = 1
f1 (generic function with 1 method)
julia> f1(::Bool) = 2
f1 (generic function with 2 methods)
julia> typeof(f1)
typeof(f1)
julia> methods(f1)
# 2 methods for generic function "f1":
[1] f1(::Bool) in Main at REPL[21]:1
[2] f1(::Int64) in Main at REPL[20]:1
現在參考您所寫的返回值規范:
f(x)::Float64 = x
它們不僅僅是斷言。 Julia 實際上做了兩件事:
這是相關的,例如在我上面的函數f
,您可以編寫:
julia> f(1)
1.0
julia> f(true)
1.0
並且您可以看到發生了轉換(不僅是斷言)。
這種風格與類型不穩定的代碼非常相關(例如,當使用DataFrame
是一種類型不穩定的數據結構時),因為這樣的斷言可以幫助“打破類型不穩定鏈”在您的代碼中(如果您的某些部分不是類型穩定的) )。
編輯
這不能通過使用
Union{Int, Bool} -> Int
嗎? 例如,對於f1
? 類型不穩定性也類似? 我猜這需要為所有輸入類型編譯代碼,從而失去 JIT 的優勢?
但是如果對於Int
返回一個Int
而對於Bool
返回一個Bool
呢? 考慮例如identity
函數。
也拿下面的例子:
julia> f(x) = ("a",)[x]
f (generic function with 1 method)
julia> @code_warntype f(2)
Variables
#self#::Core.Compiler.Const(f, false)
x::Int64
Body::String
1 ─ %1 = Core.tuple("a")::Core.Compiler.Const(("a",), false)
│ %2 = Base.getindex(%1, x)::String
└── return %2
julia> Base.return_types(f)
1-element Array{Any,1}:
Any
@code_warntype
正確地識別出,如果此函數返回某些內容,則它保證為String
,但是,如 Przemysław 建議的return_types
告訴您它是Any
。 所以你可以看到這是一件很難的事情,你不應該盲目地依賴它——假設它是由編譯器來決定它可以推斷出什么。 特別是 - 出於性能原因 - 編譯器可能會放棄進行推理,即使理論上這是可能的。
在您的問題中,您可能指的是 Haskell 提供的內容,但在 Haskell 中存在一個限制,即函數的返回值可能不取決於傳遞的參數的運行時值,而僅取決於參數的類型。 Julia 中沒有這樣的限制。
讓我給你看一些比較:
julia> struct var"typeof(foo)" end
julia> const foo = var"typeof(foo)"()
var"typeof(foo)"()
julia> (::var"typeof(foo)")(x::Float64) = 2x
julia> (::var"typeof(foo)")(x::String) = x * x
julia> foo(0.2)
0.4
julia> foo("sdf")
"sdfsdf"
julia> typeof(foo)
var"typeof(foo)"
這大約是內部發生的事情。 當您編寫function foo
,編譯器會生成類似匿名的單例結構,具有內部名稱typeof(foo)
和實例foo
。 然后所有方法或“調度組合”都在其類型上注冊。
你會發現給foo
一個像Float -> Float
或String -> String
這樣的類型是沒有意義的——函數只是單例值,它的類型是結構的類型。 該結構對其方法一無所知(在內部,在真正的編譯器中,它當然知道,但不能以類型系統可訪問的方式)。
從理論上講,您可以設計一個系統,其中函數的所有方法都以某種聯合類型收集,但由於函數類型在其共域中是協變的,而在其域中是逆變的,這會變得非常龐大和復雜。 所以它沒有完成。 話雖如此,人們正在討論用於引用方法實例本身的類型的語法,這就是您所想的。
函數定義末尾的::Float64
只是一個類型斷言(如果可能的話還進行轉換)——它不會以任何方式直接幫助編譯器。
為了理解它,讓我們考慮以下函數:
f(a, b) = a//b
對於一對Int
值,這將返回一個Rational{Int}
。 讓我們檢查一下 Julia 編譯器可以做什么:
julia> code_warntype(f, [Int,Int])
Variables
#self#::Core.Compiler.Const(f, false)
a::Int64
b::Int64
Body::Rational{Int64}
1 ─ %1 = (a // b)::Rational{Int64}
└── return %1
或者對於其他輸入對:
julia> code_warntype(f, [Int16,Int16])
Variables
#self#::Core.Compiler.Const(f, false)
a::Int16
b::Int16
Body::Rational{Int16}
1 ─ %1 = (a // b)::Rational{Int16}
└── return %1
您可以看到編譯器可以根據輸入類型計算輸出類型。 因此 Julia 代碼中的唯一規則是“始終編寫類型穩定的代碼”。 考慮函數:
g(a,b) = b != 0 ? a/b : a
讓我們測試一下類型:
julia> code_warntype(g, [Int,Int])
Variables
#self#::Core.Compiler.Const(g, false)
a::Int64
b::Int64
Body::Union{Float64, Int64}
1 ─ %1 = (b != 0)::Bool
└── goto #3 if not %1
2 ─ %3 = (a / b)::Float64
└── return %3
3 ─ return a
在 Julia REPL Union{Float64, Int64}
中顯示為紅色,這意味着代碼類型不穩定(返回 int 或 float)。
最后但並非最不重要的是,也可以要求函數輸出類型:
julia> Base.return_types(f,(Int8,Int16))
1-element Array{Any,1}:
Rational{Int16}
julia> Base.return_types(g,(Int,Float64))
1-element Array{Any,1}:
Union{Float64, Int64}
綜上所述:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.