Control de flujo en Julia
Las sentencias de control de flujo se implementan mediante uno o más condicionales establecidos por el programa. Cuando la condición es true se ejecuta el código especificado, y cuando es false se ejecuta otro código especificado.
Julia proporciona una gran cantidad de sentencias de control de flujo:
Expresiones compuestas:beginy;。
Expresiones condicionales:if-elseif-elsey?:(Operador ternario).
Evaluación de cortocircuito:Operadores lógicos&&(y) y||(o), y comparaciones en cadena.
Sentencias de bucle:Bucle:whileyfor。
Manejo de excepciones:try-catch、errorythrow。
-
Task (corrutinas):yieldto。
Expresiones compuestas
begin ... endLas expresiones pueden evaluar varias subexpresiones en orden y devolver el valor de la última subexpresión:
Ejemplo
x = 1
y = 2
x + y
end
3
Debido a que estas son expresiones muy cortas, pueden colocarse simplemente en una línea, y de ahí proviene la cadena:;Ejemplo
Ejemplo
3
En la práctica, no se requiere que el bloque begin sea multilínea, ni que la cadena ; sea de una sola línea.
Ejemplo
3
julia> (x = 1;
y = 2;
x + y)
3
Expresiones condicionales
Las expresiones condicionales determinan qué bloque de código se ejecuta según el valor de una expresión booleana.
Sintaxis de if-elseif-else:
/*Cuando la expresión booleana1es verdadera, se ejecuta*/
elseif boolean_expression 2
/*Cuando la expresión booleana2es verdadera, se ejecuta*/
elseif boolean_expression 3
/*Cuando la expresión booleana3es verdadera, se ejecuta*/
else
/*Cuando ninguna de las condiciones anteriores es verdadera, se ejecuta*/
Unaifsentencia if puede ir seguida de un opcionalelse if...elsesentencia else.
Al usar sentencias if...else if...else, ten en cuenta los siguientes puntos:
- Un if puede ir seguido de cero o un else, y el else debe ir después de todos los else if.
- Un if puede ir seguido de cero o más else if, y los else if deben ir antes del else.
- Una vez que un else if coincide correctamente, los demás else if o else no se evaluarán.
A continuación se muestra el análisis de la sintaxis condicional if-elseif-else:
Ejemplo
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
Si la expresión x < y es true, se ejecuta el bloque de código correspondiente; de lo contrario, se evalúa la condición x > y; si es true, se ejecuta el bloque correspondiente; si ninguna expresión es true, se ejecuta el bloque else.
Ejemplo
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
end
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
Operador ternario
Operador ternario?:Similar a la sintaxis de if-elseif-else:
a ? b : cEn?La expresión a anterior es una condición; si a es true, el operador ternario evalúa la expresión b antes de :; si a es false, ejecuta la expresión c después de :.
Nota:?y:Los espacios circundantes son obligatorios; una expresión como a?b:c no es un operador ternario válido (pero se permiten saltos de línea después de ? y de :).
Ejemplo
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less than
Si la expresión x < y es verdadera, todo el operador ternario ejecutará la cadena "less than"; de lo contrario, ejecutará la cadena "not less than".
Anidamiento en cadena del operador ternario:
Ejemplo
x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to y
Para facilitar el encadenamiento de valores, los operadores se asocian de derecha a izquierda.
Similar a if-elseif-else,:las expresiones anteriores y posteriores se ejecutan únicamente cuando la condición es true o false, respectivamente:
Ejemplo
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"
julia> 1 > 2 ? v("yes") : v("no")
no
"no"
Evaluación de cortocircuito
En Julia,&&y||los operadores corresponden respectivamente a las operaciones lógicas"Y"y"O"operaciones.
- En la expresión
a && b, la subexpresiónbsolo cuandoaParatruese ejecuta; si a es false, se devuelve directamente false. - En la expresión
a || b, la subexpresiónbsolo seaParafalsese ejecuta; si a es true, devuelve directamente a.
Ejemplo de &&
┌ Warning: An odd Number!
└ @ Main REPL[5]:1
julia> isodd(4) && @warn("An odd Number!")
false
Ejemplo de ||
true
julia> isodd(4) || @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[8]:1
&& y || dependen del lado derecho, pero && tiene mayor prioridad que ||, mira el ejemplo:
Ejemplo
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
julia> t(1) || t(2)
1
true
julia> t(1) || f(2)
1
true
julia> f(1) || t(2)
1
2
true
julia> f(1) || f(2)
1
2
false
Sentencias de bucle
Las sentencias de bucle utilizanwhileyfordos palabras clave para implementarse.
A continuación hay unwhileejemplo de bucle:
Ejemplo
julia> while i <= 5
println(i)
global i += 1
end
1
2
3
4
5
La sentencia while evalúa la condición (i <= 5); mientras sea true, se ejecuta el cuerpo del bucle while. Si la condición es false, es decir, cuando i=6, el bucle termina.
El bucle for es más conveniente; el ejemplo anterior se implementa con for de la siguiente manera:
Ejemplo
println(i)
end
1
2
3
4
5
Aquí,1:5es un objeto de rango que representa la secuencia de números 1, 2, 3, 4, 5.
El bucle for itera sobre estos valores, asignando a cada variable i.
Una diferencia muy importante entre el bucle for y el bucle while anterior es el ámbito, es decir, la visibilidad de las variables. Si la variable i no se ha introducido en otro ámbito, dentro del bucle for solo es visible dentro del bucle for, y no es visible fuera ni después. Necesitas una nueva sesión interactiva o un nuevo nombre de variable para probar esta característica:
Ejemplo
println(j)
end
1
2
3
4
5
julia> j
ERROR: UndefVarError: j not defined
En general, los componentes del bucle for pueden usarse para iterar sobre cualquier contenedor. En este caso, en lugar de =, la otra palabra clave (pero completamente equivalente)ino∈es más utilizada, porque hace que el código sea más claro:
Ejemplo
println(i)
end
1
4
0
julia> for s ∈ ["foo","bar","baz"]
println(s)
end
foo
bar
baz
Por conveniencia, podemos terminar un bucle while antes de que la condición deje de cumplirse, o detener un bucle for antes de llegar al final del objeto iterado. Esto se puede hacer con la palabra clavebreakpara lograrlo:
Ejemplo
julia> while true
println(i)
if i >= 5
break
end
global i += 1
end
1
2
3
4
5
julia> for j = 1:1000
println(j)
if j >= 5
break
end
end
1
2
3
4
5
Sin la palabra clave break, el bucle while anterior nunca terminaría por sí solo, y el bucle for iteraría hasta 1000. Estos bucles pueden terminarse antes usando break.
En algunos escenarios, es necesario terminar la iteración actual e inmediatamente pasar a la siguiente;continuela palabra clave se utiliza para lograr esto:
Ejemplo
if i % 3 != 0
continue
end
println(i)
end
3
6
9
Este es un ejemplo un poco artificial, porque podríamos negar la condición y poner la llamada a println dentro del bloque if para lograr lo mismo de forma más concisa. En la práctica, suele haber más código que se ejecuta después de continue, y puede haber múltiples lugares donde se llame a continue.
Varios bucles for anidados pueden combinarse en un solo bucle externo, lo que puede usarse para crear el producto cartesiano de sus objetos de iteración:
Ejemplo
println((i, j))
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
Con esta sintaxis, las variables de iteración aún pueden usarse normalmente para indexar con las variables del bucle, por ejemplofor i = 1:n, j = 1:ies válido, pero usar break dentro de un bucle salta de todo el bucle anidado, no solo del bucle interno. Cada vez que se ejecuta el bucle interno, las variables (i y j) se asignan a sus valores de iteración actuales. Por lo tanto, las asignaciones a i no son visibles para las siguientes iteraciones:
Ejemplo
println((i, j))
i = 0
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)
Si este ejemplo se reescribe con una palabra clave `for` para cada variable, la salida será diferente: la segunda y la cuarta variables contienen 0.
Se puede usar `zip` en un solo bucle `for` para iterar sobre múltiples contenedores simultáneamente:
Ejemplo
println((j,k))
end
(1, 4)
(2, 5)
(3, 6)
julia> for x in zip(0:15, 100:110, 200:210)
println(x)
end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)
// Manejar la inconsistencia en la cantidad de elementos
julia> for x in zip(0:10, 100:115, 200:210)
println(x)
end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)
Usar `zip` creará un iterador que es una tupla que contiene subiteradores de los contenedores que se le pasan. El iterador `zip` iterará sobre todos los subiteradores en orden, seleccionando el elemento `ii` de cada subiterador en la iteración `ii` del bucle `for`. Una vez que cualquier subiterador se agote, el bucle `for` se detendrá.
La declaración `for` puede anidar múltiples condiciones de bucle, usando comas,para separar:
Ejemplo
@show (n, m)
end
(n, m) = (1, 1)
(n, m) = (1, 2)
(n, m) = (1, 3)
(n, m) = (1, 4)
(n, m) = (1, 5)
(n, m) = (2, 1)
(n, m) = (2, 2)
(n, m) = (2, 3)
(n, m) = (2, 4)
(n, m) = (2, 5)
(n, m) = (3, 1)
(n, m) = (3, 2)
(n, m) = (3, 3)
(n, m) = (3, 4)
(n, m) = (3, 5)
(n, m) = (4, 1)
(n, m) = (4, 2)
(n, m) = (4, 3)
(n, m) = (4, 4)
(n, m) = (4, 5)
(n, m) = (5, 1)
(n, m) = (5, 2)
(n, m) = (5, 3)
(n, m) = (5, 4)
(n, m) = (5, 5)
Comprensiones
La comprensión es una forma única de procesamiento de datos que permite construir una nueva estructura de secuencia de datos a partir de otra secuencia de datos.El formato es el siguiente:
[out_exp_res for out_exp in input_list]
o
[expresiónforvariableinlistaifcondición]
[out_exp_res for out_exp in input_list if condition]
Ejemplo
5-element Array{Int64,1}:
1
4
9
16
25
También podemos especificar el tipo de elementos que queremos generar:
Ejemplo
5-element Array{Complex,1}:
1 + 0im
4 + 0im
9 + 0im
16 + 0im
25 + 0im
Recorrer arreglos
A veces queremos iterar sobre cada elemento de un arreglo, incluyendo el número de índice del elemento.
Julia proporcionaenumerate(iter)la función, cuyo parámetroiteres un objeto iterable; esta función generará los números de índice y los valores correspondientes a cada índice.
Ejemplo
julia> for (index, value) in enumerate(a)
println("$index $value")
end
1 a
2 b
3 c
julia> arr = rand(0:9, 4, 4)
4×4 Array{Int64,2}:
7 6 5 8
8 6 9 4
6 3 0 7
2 3 2 4
julia> [x for x in enumerate(arr)]
4×4 Array{Tuple{Int64,Int64},2}:
(1, 7) (5, 6) (9, 5) (13, 8)
(2, 8) (6, 6) (10, 9) (14, 4)
(3, 6) (7, 3) (11, 0) (15, 7)
(4, 2) (8, 3) (12, 2) (16, 4)
Manejo de excepciones
Durante la ejecución de un programa, si ocurre una condición inesperada, una función puede ser incapaz de devolver un valor razonable al llamador. En tal caso, es mejor dejar que la condición inesperada termine el programa e imprima un mensaje de error de depuración, lo que facilita a los desarrolladores el manejo de su código.
Mediantetry / catchla sentencia, se pueden manejar convenientemente las excepciones.
Por ejemplo, en el siguiente código, la función de raíz cuadrada `sqrt` lanzará una excepción. Con `try / catch` podemos generar con precisión la información de la excepción.
Ejemplo
sqrt("ten")
catch e
println("Necesitas ingresar un número")
end
Necesitas ingresar un número
Cláusula finally
Al programar cambios de estado o al usar recursos similares a archivos, a menudo es necesario realizar tareas de limpieza al final del código (por ejemplo, cerrar archivos). Dado que las excepciones hacen que algunos bloques de código salgan antes de terminar normalmente, esto puede complicar dichas tareas. La palabra clave `finally` proporciona una manera de ejecutar cierto código cuando un bloque sale, sin importar cómo salga.
Aquí hay un ejemplo que asegura que un archivo abierto se cierre:
Ejemplo
try
# operate on file f
finally
close(f)
end
Cuando el flujo de control sale del bloque `try` (por ejemplo, al encontrar un `return`, o al terminar normalmente), se ejecutará `close(f)`. Si el bloque `try` sale debido a una excepción, la excepción se seguirá propagando. El bloque `catch` se puede usar junto con `try` y `finally`. En ese caso, el bloque `finally` se ejecuta después de que `catch` maneje el error.
La función throw
Podemos usar `throw` para crear excepciones explícitamente.
Por ejemplo, si una función solo está definida para números no negativos, cuando el parámetro de entrada es negativo, se puede usar `throw` para lanzar un `DomainError`.
Ejemplo
f (generic function with 1 method)
julia> f(1)
0.36787944117144233
julia> f(-1)
ERROR: DomainError with -1:
argument must be nonnegative
Stacktrace:
[1] f(::Int64) at ./none:1
Ten en cuenta que `DomainError` sin paréntesis no es una excepción, sino un tipo de excepción. Necesitamos invocarlo para obtener un objeto `Exception`:
Ejemplo
true
julia> typeof(DomainError) <: Exception
false
Además, algunos tipos de excepción aceptan uno o más parámetros para el informe de errores:
Ejemplo
ERROR: UndefVarError: x not defined
Podemos imitar el estilo de `UndefVarError` y usar un tipo de excepción personalizado para implementar fácilmente este mecanismo:
Ejemplo
var::Symbol
end
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")
Task (corrutinas)
Task (corrutina) proporciona un control de flujo no local, lo que hace posible cambiar entre tareas de cálculo temporalmente suspendidas. Lo presentaremos en detalle en el capítulo de programación asíncrona.
Otras extensiones