课程表

Julia课程

工具箱
速查手册

Julia 控制流

当前位置:免费教程 » 程序设计 » Julia

Julia 提供一系列控制流:

前五个控制流机制是高级编程语言的标准。但任务不是:它提供了非本地的控制流,便于在临时暂停的计算中进行切换。在 Julia 中,异常处理和协同多任务都是使用的这个机制。

复合表达式

用一个表达式按照顺序对一系列子表达式求值,并返回最后一个子表达式的值,有两种方法:begin 块和 (;) 链。 begin 块的例子:

  1. julia> z = begin
  2. x = 1
  3. y = 2
  4. x + y
  5. end
  6. 3

这个块很短也很简单,可以用 (;) 链语法将其放在一行上:

  1. julia> z = (x = 1; y = 2; x + y)
  2. 3

这个语法在函数中的单行函数定义非常有用。 begin 块也可以写成单行, (;) 链也可以写成多行:

  1. julia> begin x = 1; y = 2; x + y end
  2. 3
  3. julia> (x = 1;
  4. y = 2;
  5. x + y)
  6. 3

条件求值

一个 if-elseif-else 条件表达式的例子:

  1. if x < y
  2. println("x is less than y")
  3. elseif x > y
  4. println("x is greater than y")
  5. else
  6. println("x is equal to y")
  7. end

如果条件表达式 x < y 为真,相应的语句块将会被执行;否则就执行条件表达式 x > y ,如果结果为真,相应的语句块将被执行;如果两个表达式都是假,else 语句块将被执行。这是它用在实际中的例子:

  1. julia> function test(x, y)
  2. if x < y
  3. println("x is less than y")
  4. elseif x > y
  5. println("x is greater than y")
  6. else
  7. println("x is equal to y")
  8. end
  9. end
  10. test (generic function with 1 method)
  11. julia> test(1, 2)
  12. x is less than y
  13. julia> test(2, 1)
  14. x is greater than y
  15. julia> test(1, 1)
  16. x is equal to y

elseifelse 块是可选的。

请注意,非常短的条件语句(一行)在 Julia 中是会经常使用短的电路评估(Short-Circuit Evaluation)实现的,具体细节在下一节中进行概述。

如果条件表达式的值是除 truefalse 之外的值,会出错:

  1. julia> if 1
  2. println("true")
  3. end
  4. ERROR: type: non-boolean (Int64) used in boolean context

“问号表达式”语法 ?:if-elseif-else 语法相关,但是适用于单个表达式:

  1. a ? b : c

? 之前的 a 是条件表达式,如果为 true ,就执行 : 之前的 b 表达式,如果为 false ,就执行 :c 表达式。

用问号表达式来重写,可以使前面的例子更加紧凑。先看一个二选一的例子:

  1. julia> x = 1; y = 2;
  2. julia> println(x < y ? "less than" : "not less than")
  3. less than
  4. julia> x = 1; y = 0;
  5. julia> println(x < y ? "less than" : "not less than")
  6. not less than

三选一的例子需要链式调用问号表达式:

  1. julia> test(x, y) = println(x < y ? "x is less than y" :
  2. x > y ? "x is greater than y" : "x is equal to y")
  3. test (generic function with 1 method)
  4. julia> test(1, 2)
  5. x is less than y
  6. julia> test(2, 1)
  7. x is greater than y
  8. julia> test(1, 1)
  9. x is equal to y

链式问号表达式的结合规则是从右到左。

if-elseif-else 类似,: 前后的表达式,只有在对应条件表达式为 truefalse 时才执行:

  1. julia> v(x) = (println(x); x)
  2. v (generic function with 1 method)
  3. julia> 1 < 2 ? v("yes") : v("no")
  4. yes
  5. "yes"
  6. julia> 1 > 2 ? v("yes") : v("no")
  7. no
  8. "no"

短路求值

&&|| 布尔运算符被称为短路求值,它们连接一系列布尔表达式,仅计算最少的表达式来确定整个链的布尔值。这意味着: 在表达式 a && b 中,只有 atrue 时才计算子表达式 b 在表达式 a || b 中,只有 afalse 时才计算子表达式 b &&|| 都与右侧结合,但 &&|| 优先级高:

  1. julia> t(x) = (println(x); true)
  2. t (generic function with 1 method)
  3. julia> f(x) = (println(x); false)
  4. f (generic function with 1 method)
  5. julia> t(1) && t(2)
  6. 1
  7. 2
  8. true
  9. julia> t(1) && f(2)
  10. 1
  11. 2
  12. false
  13. julia> f(1) && t(2)
  14. 1
  15. false
  16. julia> f(1) && f(2)
  17. 1
  18. false
  19. julia> t(1) || t(2)
  20. 1
  21. true
  22. julia> t(1) || f(2)
  23. 1
  24. true
  25. julia> f(1) || t(2)
  26. 1
  27. 2
  28. true
  29. julia> f(1) || f(2)
  30. 1
  31. 2
  32. false

这种方式在 Julia 里经常作为 if 语句的一个简洁的替代。 可以把 if <cond> <statement> end 写成 <cond> && <statement> (读作 <cond> *从而* <statement>)。 类似地, 可以把 if ! <cond> <statement> end 写成 <cond> || <statement> (读作 要不就 )。

例如, 递归阶乘可以这样写:

  1. julia> function factorial(n::Int)
  2. n >= 0 || error("n must be non-negative")
  3. n == 0 && return 1
  4. n * factorial(n-1)
  5. end
  6. factorial (generic function with 1 method)
  7. julia> factorial(5)
  8. 120
  9. julia> factorial(0)
  10. 1
  11. julia> factorial(-1)
  12. ERROR: n must be non-negative
  13. in factorial at none:2

短路求值运算符,可以使用数学运算和基本函数中介绍的位布尔运算符 &|

  1. julia> f(1) & t(2)
  2. 1
  3. 2
  4. false
  5. julia> t(1) | t(2)
  6. 1
  7. 2
  8. true

&&|| 的运算对象也必须是布尔值( truefalse )。在任何地方使用一个非布尔值,除非最后一个进入连锁条件的是一个错误:

  1. julia> 1 && true
  2. ERROR: type: non-boolean (Int64) used in boolean context

另一方面,任何类型的表达式可以使用在一个条件链的末端。根据前面的条件,它将被评估和返回:

  1. julia> true && (x = rand(2,2))
  2. 2x2 Array{Float64,2}:
  3. 0.768448 0.673959
  4. 0.940515 0.395453
  5. julia> false && (x = rand(2,2))
  6. false

重复求值: 循环

有两种循环表达式: while 循环和 for 循环。下面是 while 的例子:

  1. julia> i = 1;
  2. julia> while i <= 5
  3. println(i)
  4. i += 1
  5. end
  6. 1
  7. 2
  8. 3
  9. 4
  10. 5

上例也可以重写为 for 循环:

  1. julia> for i = 1:5
  2. println(i)
  3. end
  4. 1
  5. 2
  6. 3
  7. 4
  8. 5

此处的 1:5 是一个 Range 对象,表示的是 1, 2, 3, 4, 5 序列。 for 循环遍历这些数,将其逐一赋给变量 iwhile 循环和 for 循环的另一区别是变量的作用域。如果在其它作用域中没有引入变量 i ,那么它仅存在于 for 循环中。不难验证:

  1. julia> for j = 1:5
  2. println(j)
  3. end
  4. 1
  5. 2
  6. 3
  7. 4
  8. 5
  9. julia> j
  10. ERROR: j not defined

有关变量作用域,详见变量的作用域

通常,for 循环可以遍历任意容器。这时,应使用另一个(但是完全等价的)关键词 in ,而不是 = ,它使得代码更易阅读:

  1. julia> for i in [1,4,0]
  2. println(i)
  3. end
  4. 1
  5. 4
  6. 0
  7. julia> for s in ["foo","bar","baz"]
  8. println(s)
  9. end
  10. foo
  11. bar
  12. baz

手册中将介绍各种可迭代容器(详见多维数组)。

有时要提前终止 whilefor 循环。可以通过关键词 break 来实现:

  1. julia> i = 1;
  2. julia> while true
  3. println(i)
  4. if i >= 5
  5. break
  6. end
  7. i += 1
  8. end
  9. 1
  10. 2
  11. 3
  12. 4
  13. 5
  14. julia> for i = 1:1000
  15. println(i)
  16. if i >= 5
  17. break
  18. end
  19. end
  20. 1
  21. 2
  22. 3
  23. 4
  24. 5

有时需要中断本次循环,进行下一次循环,这时可以用关键字 continue

  1. julia> for i = 1:10
  2. if i % 3 != 0
  3. continue
  4. end
  5. println(i)
  6. end
  7. 3
  8. 6
  9. 9

多层 for 循环可以被重写为一个外层循环,迭代类似于笛卡尔乘积的形式:

  1. julia> for i = 1:2, j = 3:4
  2. println((i, j))
  3. end
  4. (1,3)
  5. (1,4)
  6. (2,3)
  7. (2,4)

这种情况下用 break 可以直接跳出所有循环。

异常处理

当遇到意外条件时,函数可能无法给调用者返回一个合理值。这时,要么终止程序,打印诊断错误信息;要么程序员编写异常处理。

内置异常 Exception

如果程序遇到意外条件,异常将会被抛出。表中列出内置异常。

Exception
ArgumentError
BoundsError
DivideError
DomainError
EOFError
ErrorException
InexactError
InterruptException
KeyError
LoadError
MemoryError
MethodError
OverflowError
ParseError
SystemError
TypeError
UndefRefError
UndefVarError

例如,当对负实数使用内置的 sqrt 函数时,将抛出 DomainError()

  1. julia> sqrt(-1)
  2. ERROR: DomainError
  3. sqrt will only return a complex result if called with a complex argument.
  4. try sqrt(complex(x))
  5. in sqrt at math.jl:131

你可以使用下列方式定义你自己的异常:

  1. julia> type MyCustomException <: Exception end

throw 函数

可以使用 throw 函数显式创建异常。例如,某个函数只对非负数做了定义,如果参数为负数,可以抛出 DomaineError 异常:

  1. julia> f(x) = x>=0 ? exp(-x) : throw(DomainError())
  2. f (generic function with 1 method)
  3. julia> f(1)
  4. 0.36787944117144233
  5. julia> f(-1)
  6. ERROR: DomainError
  7. in f at none:1

注意,DomainError 使用时需要使用带括号的形式,否则返回的并不是异常,而是异常的类型。必须带括号才能返回 Exception 对象:

  1. julia> typeof(DomainError()) <: Exception
  2. true
  3. julia> typeof(DomainError) <: Exception
  4. false

另外,一些异常类型使用一个或更多个参数用来报告错误:

  1. julia> throw(UndefVarError(:x))
  2. ERROR: x not defined

这个机制能被简单实现,通过按照下列所示的 UndefVarError 方法自定义异常类型:

  1. julia> type MyUndefVarError <: Exception
  2. var::Symbol
  3. end
  4. julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined");

error 函数

error 函数用来产生 ErrorException ,阻断程序的正常执行。

如下改写 sqrt 函数,当参数为负数时,提示错误,立即停止执行:

  1. julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
  2. fussy_sqrt (generic function with 1 method)
  3. julia> fussy_sqrt(2)
  4. 1.4142135623730951
  5. julia> fussy_sqrt(-1)
  6. ERROR: negative x not allowed
  7. in fussy_sqrt at none:1

当对负数调用 fussy_sqrt 时,它会立即返回,显示错误信息:

  1. julia> function verbose_fussy_sqrt(x)
  2. println("before fussy_sqrt")
  3. r = fussy_sqrt(x)
  4. println("after fussy_sqrt")
  5. return r
  6. end
  7. verbose_fussy_sqrt (generic function with 1 method)
  8. julia> verbose_fussy_sqrt(2)
  9. before fussy_sqrt
  10. after fussy_sqrt
  11. 1.4142135623730951
  12. julia> verbose_fussy_sqrt(-1)
  13. before fussy_sqrt
  14. ERROR: negative x not allowed
  15. in verbose_fussy_sqrt at none:3

warninfo 函数

Julia 还提供一些函数,用来向标准错误 I/O 输出一些消息,但不抛出异常,因而并不会打断程序的执行:

  1. julia> info("Hi"); 1+1
  2. INFO: Hi
  3. 2
  4. julia> warn("Hi"); 1+1
  5. WARNING: Hi
  6. 2
  7. julia> error("Hi"); 1+1
  8. ERROR: Hi
  9. in error at error.jl:21

try/catch 语句

try/catch 语句可以用于处理一部分预料中的异常 Exception 。例如,下面求平方根函数可以正确处理实数或者复数:

  1. julia> f(x) = try
  2. sqrt(x)
  3. catch
  4. sqrt(complex(x, 0))
  5. end
  6. f (generic function with 1 method)
  7. julia> f(1)
  8. 1.0
  9. julia> f(-1)
  10. 0.0 + 1.0im

但是处理异常比正常采用分支来处理,会慢得多。

try/catch 语句使用时也可以把异常赋值给某个变量。例如:

  1. julia> sqrt_second(x) = try
  2. sqrt(x[2])
  3. catch y
  4. if isa(y, DomainError)
  5. sqrt(complex(x[2], 0))
  6. elseif isa(y, BoundsError)
  7. sqrt(x)
  8. end
  9. end
  10. sqrt_second (generic function with 1 method)
  11. julia> sqrt_second([1 4])
  12. 2.0
  13. julia> sqrt_second([1 -4])
  14. 0.0 + 2.0im
  15. julia> sqrt_second(9)
  16. 3.0
  17. julia> sqrt_second(-9)
  18. ERROR: DomainError
  19. in sqrt_second at none:7

注意,跟在 catch 之后的符号会被解释为一个异常的名称,因此,需要注意的是,在单行中写 try/catch 表达式时。下面的代码将不会正常工作返回 x 的值为了防止发生错误:

  1. try bad() catch x end

我们在 catch 后使用分号或插入换行来实现:

  1. try bad() catch; x end
  2. try bad()
  3. catch
  4. x
  5. end

Julia 还提供了更高级的异常处理函数 rethrowbacktracecatch_backtrace

finally 语句

在改变状态或者使用文件等资源时,通常需要在操作执行完成时做清理工作(比如关闭文件)。异常的存在使得这样的任务变得复杂,因为异常会导致程序提前退出。关键字 finally 可以解决这样的问题,无论程序是怎样退出的,finally 语句总是会被执行。

例如, 下面的程序说明了怎样保证打开的文件总是会被关闭:

  1. f = open("file")
  2. try
  3. # operate on file f
  4. finally
  5. close(f)
  6. end

当程序执行完 try 语句块(例如因为执行到 return 语句,或者只是正常完成),close 语句将会被执行。如果 try 语句块因为异常提前退出,异常将会继续传播。catch 语句可以和 tryfinally 一起使用。这时。finally 语句将会在 catch 处理完异常之后执行。

任务(也称为协程)

任务是一种允许计算灵活地挂起和恢复的控制流,有时也被称为对称协程、轻量级线程、协同多任务等。

如果一个计算(比如运行一个函数)被设计为 Task,有可能因为切换到其它 Task 而被中断。原先的 Task 在以后恢复时,会从原先中断的地方继续工作。切换任务不需要任何空间,同时可以有任意数量的任务切换,而不需要考虑堆栈问题。任务切换与函数调用不同,可以按照任何顺序来进行。

任务比较适合生产者-消费者模式,一个过程用来生产值,另一个用来消费值。消费者不能简单的调用生产者来得到值,因为两者的执行时间不一定协同。在任务中,两者则可以正常运行。

Julia 提供了 produceconsume 函数来解决这个问题。生产者调用 produce 函数来生产值:

  1. julia> function producer()
  2. produce("start")
  3. for n=1:4
  4. produce(2n)
  5. end
  6. produce("stop")
  7. end;

要消费生产的值,先对生产者调用 Task 函数,然后对返回的对象重复调用 consume

  1. julia> p = Task(producer);
  2. julia> consume(p)
  3. "start"
  4. julia> consume(p)
  5. 2
  6. julia> consume(p)
  7. 4
  8. julia> consume(p)
  9. 6
  10. julia> consume(p)
  11. 8
  12. julia> consume(p)
  13. "stop"

可以在 for 循环中迭代任务,生产的值被赋值给循环变量:

  1. julia> for x in Task(producer)
  2. println(x)
  3. end
  4. start
  5. 2
  6. 4
  7. 6
  8. 8
  9. stop

注意 Task() 函数的参数,应为零参函数。生产者常常是参数化的,因此需要为其构造零参匿名函数 。可以直接写,也可以调用宏:

  1. function mytask(myarg)
  2. ...
  3. end
  4. taskHdl = Task(() -> mytask(7))
  5. # 也可以写成
  6. taskHdl = @task mytask(7)

produceconsume 但它并不在不同的 CPU 发起线程。我们将在并行计算中,讨论真正的内核线程。

核心任务操作

尽管 produceconsume 已经阐释了任务的本质,但是他们实际上是由库函数调用更原始的函数 yieldto 实现的。 yieldto(task,value) 挂起当前任务,切换到特定的 task , 并使这个 task 的最后一次 yeidlto 返回 \特定的 value。注意 yieldto 是唯一需要的操作来进行 ‘任务风格’的控制流;不需要调用和返回,我们只用在不同的任务之间切换即可。 这就是为什么这个特性被称做 “对称式协程”;每一个任务的切换都是用相同的机制。

yeildto 很强大, 但是大多数时候并不直接调用它。 当你从当前的任务切换走,你有可能会想切换回来, 但需要知道切换的时机和任务,这会需要相当的协调。 例如,procude 需要保持某个状态来记录消费者。无需手动地记录正在消费的任务让 produceyieldto 更容易使用。

除此之外,为了高效地使用任务,其他一些基本的函数也同样必须。current_task() 获得当前运行任务的引用。istaskdone(t) 查询任务是否终止。istaskstarted(t) 查询任务是否启动。task_local_storage 处理当前任务的键值储存。

任务与事件

大多数任务的切换都是在等待像 I/O 请求这样的事件的时候,并由标准库的调度器完成。调度器记录正在运行的任务的队列,并执行一个循环来根据外部事件(比如消息到达)重启任务。

处理等待事件的基本函数是 wait。 有几种对象实现了 wait,比如对于 Process 对象, wait 会等待它终止。更多的时候 wait 是隐式的,比如 wait 可以发生在调用 read 的时候,等待数据变得可用。

在所有的情况中, wait 最终会操作在一个负责将任务排队和重启的 Condition 对象上。当任务在 Condition 上调用 wait, 任务会被标记为不可运行,被加入到 Condition 的 队列中,再切换至调度器。调度器会选取另一个任务来运行,或者等待外部事件。如果一切正常,最终一个事件句柄会在 Condition 上调用 notify,使正在等待的任务变得可以运行。

调用 Task 可以生成一个初始对调度器还未知的任务,这允许你用 yieldto 手动管理任务。不管怎样,当这样的任务正在等待事件时,事件一旦发生,它仍然会自动重启。而且任何时候你都可以调用 schedule(task) 或者用宏 @schedule@async 来让调度器来运行一个任务,根本不用去等待任何事件。(参见并行计算)

任务状态

任务包含一个 state 域,它用来描述任务的执行状态。任务状态取如下的几种符号中的一种:

符号 意义
:runnable 任务正在运行,或可被切换到该任务
:waiting 等待一个特定事件从而阻塞
:queued 在调度程序的运行队列中准备重新启动
:done 成功执行完毕
:failed 由于未处理的异常而终止
转载本站内容时,请务必注明来自W3xue,违者必究。
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号