begin ... end 表达式的实际意义何在?

z = begin
x = 1
y = 2
x + y
end
下边的代码似乎等同:
x = 1
y = 2
z = x + y
begin … end 表达式的实际意义何在?

主要就是方便写
例如

  • 贴进 REPL 里,防止它输入一行运行一行产生奇怪的行为
  • 方便用一些宏,如 @testset
  • 使得一些变量没必要占空间

谢谢谢谢谢谢谢谢谢谢

begin ... end 的作用是把一段代码当成一个整体(block of code)。

julia> x = 1
1

julia> y = 2
2

julia> z = x + y
3

是3个表达式,会返回三次结果。

julia> z = begin
       x = 1
       y = 2
       x + y
       end
3

是一个表达式,只会返回一次结果。

这样的好处主要体现在元编程的时候,用 begin ... end 括起来的代码可以很方便地操作,比如下面的代码:

using Plots
julia> @macroexpand @recipe function f(r::Result; ε_max = 0.5)
           # set a default value for an attribute with `-->`
           xlabel --> "x"
           yguide --> "y"
           markershape --> :diamond
           # add a series for an error band
           @series begin
               # force an argument with `:=`
               seriestype := :path
               # ignore series in legend and color cycling
               primary := false
               linecolor := nothing
               fillcolor := :lightgray
               fillalpha := 0.5
               fillrange := r.y .- r.ε
               # ensure no markers are shown for the error band
               markershape := :none
               # return series data
               r.x, r.y .+ r.ε
           end
           # get the seriescolor passed by the user
           c = get(plotattributes, :seriescolor, :auto)
           # highlight big errors, otherwise use the user-defined color
           markercolor := ifelse.(r.ε .> ε_max, :red, c)
           # return data
           r.x, r.y
       end
:(function (RecipesBase).apply_recipe(plotattributes::AbstractDict{Symbol, Any}, r::Result)
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:296 =#
      $(Expr(:meta, :nospecialize))
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:297 =#
      begin
          ε_max = get!(plotattributes, :ε_max, 0.5)
      end
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:298 =#
      begin
          (RecipesBase).is_key_supported(:ε_max) || delete!(plotattributes, :ε_max)
      end
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:299 =#
      series_list = (RecipesBase).RecipeData[]
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:300 =#
      func_return = begin
              #= REPL[8]:1 =#
              #= REPL[8]:3 =#
              (RecipesBase).is_explicit(plotattributes, :xlabel) || (plotattributes[:xlabel] = "x")
              #= REPL[8]:4 =#
              (RecipesBase).is_explicit(plotattributes, :yguide) || (plotattributes[:yguide] = "y")
              #= REPL[8]:5 =#
              (RecipesBase).is_explicit(plotattributes, :markershape) || (plotattributes[:markershape] = :diamond)
              #= REPL[8]:7 =#
              begin
                  #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:339 =#
                  let plotattributes = copy(plotattributes)
                      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:340 =#
                      args = begin
                              #= REPL[8]:9 =#
                              plotattributes[:seriestype] = :path
                              #= REPL[8]:11 =#
                              plotattributes[:primary] = false
                              #= REPL[8]:12 =#
                              plotattributes[:linecolor] = nothing
                              #= REPL[8]:13 =#
                              plotattributes[:fillcolor] = :lightgray
                              #= REPL[8]:14 =#
                              plotattributes[:fillalpha] = 0.5
                              #= REPL[8]:15 =#
                              plotattributes[:fillrange] = r.y .- r.ε
                              #= REPL[8]:17 =#
                              plotattributes[:markershape] = :none
                              #= REPL[8]:19 =#
                              (r.x, r.y .+ r.ε)
                          end
                      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:341 =#
                      push!(series_list, (RecipesBase).RecipeData(plotattributes, (RecipesBase).wrap_tuple(args)))
                      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:345 =#
                      nothing
                  end
              end
              #= REPL[8]:22 =#
              c = get(plotattributes, :seriescolor, :auto)
              #= REPL[8]:24 =#
              plotattributes[:markercolor] = ifelse.(r.ε .> ε_max, :red, c)
              #= REPL[8]:26 =#
              (r.x, r.y)
          end
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:301 =#
      func_return === nothing || push!(series_list, (RecipesBase).RecipeData(plotattributes, (RecipesBase).wrap_tuple(func_return)))
      #= /Users/qz/.julia/packages/RecipesBase/BRe07/src/RecipesBase.jl:308 =#
      series_list
  end)

你数数里面嵌套了多少个 begin ... end, begin ... end 之中可以插入任意行数的代码。

所以如果你用过 Pluto.jl, 就知道它不允许你一个 cell 里面有多个表达式,这时候就必须要把多个表达式用 begin ... end 包裹起来,当成一个表达式。

begin ... end 还很适合和 macros 配套使用,除了上面的 @series begin ... end, 更常用的是比如 @eval:

for (op, Ty, Tz) in ((:*, Real, :P),
                   (:/, :P, Float64), (:/, Real, :P))
    @eval begin
        function ($op)(X::StridedArray{P}, y::$Ty) where P<:Period
            # depwarn("non-broadcasted arithmetic is deprecated for Dates.TimeType; use broadcasting instead", nothing)
            Z = similar(X, $Tz)
            for (Idst, Isrc) in zip(eachindex(Z), eachindex(X))
                @inbounds Z[Idst] = ($op)(X[Isrc], y)
            end
            return Z
        end
    end
end

这样写多优雅呀。同样的代码用 Base.eval 来写就会更麻烦一点。

基于同样的原因,可以在 debugging 的时候使用 begin ... end, 比如我想debug下面这个 onliner:

f(x) = "original code"

就可以用

f(x) = begin
 #debugging code
 "original code"
end

就是一个比较容易地把单行代码变成多行代码的方法。反正不管你中间怎么改,begin ... end 只返回最后一行的值。

3 个赞