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

julia> z = begin
           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

julia> z = (x = 1; y = 2; x + y)
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

julia> begin x = 1; y = 2; x + y end
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:

if boolean_expression 1
   /*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

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

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

julia> function test(x, y)
           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 : c

En?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> x = 1; y = 2;

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

julia> test(x, y) = println(x < y ? "x is less than y"    :
                            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

julia> v(x) = (println(x); x)
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óna && b, la subexpresiónbsolo cuandoaParatruese ejecuta; si a es false, se devuelve directamente false.
  • En la expresióna || b, la subexpresiónbsolo seaParafalsese ejecuta; si a es true, devuelve directamente a.

Ejemplo de &&

julia> isodd(3) && @warn("An odd Number!")
┌ Warning: An odd Number!
└ @ Main REPL[5]:1

julia> isodd(4) && @warn("An odd Number!")
false

Ejemplo de ||

julia> isodd(3) || @warn("An odd Number!")
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

julia> t(x) = (println(x); true)
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> i = 1;

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

julia> for i = 1:5
           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

julia> for j = 1:5
           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

julia> for i in [1,4,0]
           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> i = 1;

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

julia> for i = 1:10
           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

julia> for i = 1:2, j = 3:4
           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

julia> for i = 1:2, j = 3:4
           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

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
           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

julia> for n in 1:5, m in 1:5
                  @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:

[expresiónforvariableinlista]
[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

julia> [X^2 for X in 1:5]
5-element Array{Int64,1}:
 1
 4
 9
 16
 25

También podemos especificar el tipo de elementos que queremos generar:

Ejemplo

julia> Complex[X^2 for X in 1:5]
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> a = ["a", "b", "c"];

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

julia> try
         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

f = open("file")
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

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be nonnegative"))
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

julia> typeof(DomainError(nothing)) <: Exception
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

julia> throw(UndefVarError(:x))
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

julia> struct MyUndefVarError <: Exception
           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