Function Julia函数的类型

Function Julia函数的类型,function,types,julia,Function,Types,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)。这是什么 我在查看时发现了这一点。函数定义末尾的::Float64只是一个类型断言(以及

考虑以下代码位:

julia> function foo(x::Float64)::Float64
           return 2x
       end
foo (generic function with 1 method)

julia> typeof(foo)
typeof(foo)
typeof(foo)
不返回更有意义的内容肯定有原因,例如
(Float64->Float64)
。这是什么


我在查看时发现了这一点。

函数定义末尾的
::Float64
只是一个类型断言(以及转换,如果可能的话)-它在任何方面都不会直接帮助编译器

为了理解这一点,让我们考虑以下功能:

f(a, b) = a//b
g(a,b) = b != 0 ? a/b : a
对于一对
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代码中的唯一规则是“始终编写类型稳定的代码”。 考虑函数:

f(a, b) = a//b
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中函数的输出类型,需要所有输入类型(不仅仅是方法名称)

    • 当您编写
      typeof(foo)
      时,您询问的是函数的类型。此函数可以有多个方法(类型是否稳定,但这是另一个问题),这些方法具有不同的签名(参数类型),对于其中一些方法,编译器可能能够推断返回类型,而对于其他方法,则可能无法推断返回类型(而且你不应该完全依赖它——只要假设大部分时间编译器都做对了工作)

      给出一个例子,考虑这个代码(1个函数,2个方法):

      现在参考您所写的返回值规范:

      f(x)::Float64 = x
      
      它们不仅仅是断言。朱莉娅实际上做了两件事:

      • 它将返回值转换为请求的类型
      • 转换成功的断言是否正确
      这是相关的,例如,在上面的my function
      f
      中,您可以编写:

      julia> f(1)
      1.0
      
      julia> f(true)
      1.0
      
      您可以看到转换(不仅仅是断言)发生了

      这种风格在类型不稳定的代码中非常相关(例如,当使用类型不稳定的数据结构
      DataFrame
      时),因为这样的断言可以帮助您的代码中“打破类型不稳定链”(如果您的代码中有一些部分不是类型稳定的)


      编辑


      例如,对于
      f1
      ,使用
      Union{Int,Bool}->Int
      ?以及类似的类型不稳定性?我想这将需要为所有输入类型编译代码,因此失去JIT的优势

      <如果>对于<代码> int >代码>返回一个<代码> int <代码>,对于<代码> BOOL 返回一个<代码> BOOL >考虑例如<代码>标识< /代码>函数。< /P> 还可以举下面的例子:

      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\u warntype
      正确地识别出,如果这个函数返回某个东西,它保证是
      String
      ,但是,正如Przemysław建议的那样,
      return\u 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)"
      
      这大约是内部发生的情况。当您编写
      函数foo
      时,编译器会生成一个类似于具有内部名称
      typeof(foo)
      和实例
      foo
      的异常单例结构。然后,所有方法或“分派组合”都会在其类型上注册

      你看,给
      foo
      一个类似
      Float->Float
      String->String
      的类型是没有意义的——函数只是单例值,它的类型是结构的类型。结构对它的方法一无所知(当然,在真正的编译器中,它在内部是这样做的,但不是以类型系统可以访问的方式)


      从理论上讲,你可以设计一个系统,其中一个函数的所有方法都收集在某种联合类型中,但由于函数类型在其余域中是协变的,而在其域中是逆变的,因此这一系统变得非常庞大和复杂。因此,还没有完成。也就是说,人们使用语法来引用类型o方法实例本身,这正是您所想的。

      我认为返回类型规范在某些情况下有助于编译器,正如我在回答中所评论的那样。这不能通过使用
      Union{Int,Bool}来解决吗->Int
      ?例如,对于
      f1
      ?以及类似的类型不稳定性?我猜这将要求为所有输入类型编译代码,因此失去JIT的优势?问题可以通过子类型解决,但它在许多方面变得复杂。我们可以使用
      Intersect{Int->Int,Bool->Int}
      。但我们希望它是的子类型,例如,
      Intersect{Integer->Real,String->Ref,Bool->Number}
      。或
      Integer->Real
      。或的子类型