簡體   English   中英

Julia 中的函數類型

[英]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 -> FloatString -> 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}

綜上所述:

  • 不需要聲明 Julia 函數的輸出類型 - 這是編譯器的工作
  • 定義您自己的輸出類型就像類型斷言一樣(或可用於結果類型轉換 - 請參閱 Bogumil 的回答)
  • 為了確定 Julia 中函數的輸出類型,需要所有輸入類型(不僅僅是方法名稱)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM