6 hombres inesperados de Julia


Finalmente, apareci贸 una gu铆a en ruso para el idioma Julia . Proporciona una introducci贸n completa al lenguaje para aquellos que tienen poca experiencia en programaci贸n (el resto ser谩 煤til para el desarrollo general), tambi茅n hay una introducci贸n al aprendizaje autom谩tico y un mont贸n de tareas para consolidar el material.


Durante la b煤squeda, me encontr茅 con un curso de programaci贸n para economistas (adem谩s de Julia, tambi茅n hay Python). Los experimentados pueden ir a un curso r谩pido o leer el libro C贸mo pensar como un inform谩tico


La siguiente es una traducci贸n del material del blog de Christopher Rackauckas 7 Julia Gotchas y C贸mo manejarlos


Perm铆tanme comenzar diciendo que Julia es un lenguaje maravilloso. La amo, esto es lo que considero el lenguaje m谩s poderoso e intuitivo que he usado. Este es sin duda mi idioma favorito. Sin embargo, hay algunas "trampas", peque帽as cosas dif铆ciles que debes saber. Cada idioma los tiene, y una de las primeras cosas que debe hacer para dominar el idioma es descubrir cu谩les son y c贸mo evitarlos. El objetivo de esta publicaci贸n es ayudarlo a acelerar este proceso al exponer algunos de los m谩s comunes que sugieren m茅todos de programaci贸n alternativos.


Julia es un buen lenguaje para entender lo que est谩 sucediendo, porque no tiene magia. Los desarrolladores de Julia quer铆an tener reglas de conducta claramente definidas. Esto significa que todo comportamiento puede explicarse. Sin embargo, esto puede significar que tiene que esforzarse para comprender por qu茅 esto y no lo otro est谩 sucediendo. Es por eso que no solo voy a describir algunos problemas comunes, sino que tambi茅n voy a explicar por qu茅 surgen. Ver谩s que hay algunos patrones muy similares, y una vez que te des cuenta de ellos, ya no te burlar谩s de ninguno de ellos. Debido a esto, Julia tiene una curva de aprendizaje un poco m谩s pronunciada en comparaci贸n con lenguajes m谩s simples como MATLAB / R / Python . Sin embargo, una vez que haya dominado esto, podr谩 utilizar completamente el laconismo de Julia para obtener el rendimiento de C / Fortran . Ahora cava m谩s profundo.


Inesperadamente: REPL (terminal) tiene un alcance global


Este es, con mucho, el problema m谩s com煤n reportado por los nuevos usuarios de Julia. Alguien dir谩: "Escuch茅, 隆Julia es r谩pida!", Abra REPL, escriba r谩pidamente un algoritmo conocido y ejecute este script. Despu茅s de su ejecuci贸n, miran el tiempo y dicen: "Espera un segundo, 驴por qu茅 es lento, como en Python?" Dado que este es un problema tan importante y com煤n, dediquemos un tiempo a explorar las razones por las que esto sucede para descubrir c贸mo evitarlo.


Una peque帽a digresi贸n: por qu茅 Julia es r谩pida


Debe comprender que Julia no es solo una compilaci贸n de c贸digo, sino tambi茅n una especializaci贸n de tipos (es decir, una compilaci贸n de c贸digo espec铆fica para estos tipos). Perm铆tanme reiterar: Julia no es r谩pida, porque el c贸digo se compila utilizando el compilador JIT, m谩s bien el secreto de la velocidad es que se compila el c贸digo espec铆fico del tipo.


Si necesita una historia completa, consulte algunas de las notas que escrib铆 para el pr贸ximo seminario . La especificidad de tipo est谩 determinada por el principio b谩sico del dise帽o de Julia: despacho m煤ltiple . Cuando escribes el c贸digo:


 function f(a,b) return 2a+b end 

Parece que esta es solo una , pero de hecho aqu铆 se crean una gran cantidad de . En el lenguaje de Julia, una funci贸n es una abstracci贸n, y lo que en realidad se llama es un m茅todo. Si llama a f(2.0,3.0) , Julia ejecutar谩 el c贸digo compilado, que toma dos n煤meros de coma flotante y devuelve 2a + b . Si llama a f(2,3) , Julia ejecutar谩 otro c贸digo compilado que toma dos enteros y devuelve 2a + b . La funci贸n f es una abstracci贸n o abreviatura para muchos m茅todos diferentes que tienen la misma forma, y 鈥嬧媎icho esquema de usar el s铆mbolo f para llamar a todos estos m茅todos diferentes se denomina despacho m煤ltiple. Y esto se aplica en todas partes: el operador + es en realidad una funci贸n que llamar谩 a los m茅todos dependiendo de los tipos que vea. En realidad, Julia obtiene su velocidad porque el c贸digo compilado por ella conoce sus tipos y, por lo tanto, el c贸digo compilado que llama a f (2.0,3.0) es exactamente el c贸digo compilado que se obtiene al definir la misma funci贸n en C / Fortran . Puede verificar esto con la macro code_native para ver el ensamblado compilado:


 @code_native f(2.0,3.0) 

 pushq %rbp movq %rsp, %rbp Source line: 2 vaddsd %xmm0, %xmm0, %xmm0 vaddsd %xmm1, %xmm0, %xmm0 popq %rbp retq nop 

Este es el mismo ensamblado compilado que espera de una funci贸n en C / Fortran , y difiere del c贸digo de ensamblado para enteros:


 @code_native f(2,3) pushq %rbp movq %rsp, %rbp Source line: 2 leaq (%rdx,%rcx,2), %rax popq %rbp retq nopw (%rax,%rax) 

Esencia: REPL / Global Scope no permite especificidad de tipo


Esto nos lleva al punto principal: REPL / Global Scope es lento porque no permite la especificaci贸n de tipo. En primer lugar, tenga en cuenta que REPL es un 谩mbito global porque Julia permite un 谩mbito anidado para funciones. Por ejemplo, si definimos


 function outer() a = 5 function inner() return 2a end b = inner() return 3a+b end 

veremos que este c贸digo funciona. Esto se debe a que Julia le permite capturar funci贸n externa a una funci贸n interna. Si aplica esta idea de manera recursiva, se dar谩 cuenta de que el 谩rea m谩s alta es el 谩rea que es REPL directamente (que es el alcance global del m贸dulo Principal ). Pero ahora pensemos c贸mo se compilar谩 la funci贸n en esta situaci贸n. Implementamos lo mismo, pero usando variables globales:


 a=2.0; b=3.0 function linearcombo() return 2a+b end ans = linearcombo() 

y


 a = 2; b = 3 ans2= linearcombo() 

Pregunta: 驴Qu茅 tipos debe aceptar el compilador para b ? Tenga en cuenta que en este ejemplo, cambiamos los tipos y a煤n llamamos a la misma funci贸n. Puede tratar cualquier tipo que le agreguemos: flotante, enteros, matrices, tipos de usuarios extra帽os, etc. En Julia, esto significa que las variables deben estar encuadradas y los tipos se verifican cada vez que se usan. 驴C贸mo crees que se ve el c贸digo compilado?


Voluminoso
 pushq %rbp movq %rsp, %rbp pushq %r15 pushq %r14 pushq %r12 pushq %rsi pushq %rdi pushq %rbx subq $96, %rsp movl $2147565792, %edi # imm = 0x800140E0 movabsq $jl_get_ptls_states, %rax callq *%rax movq %rax, %rsi leaq -72(%rbp), %r14 movq $0, -88(%rbp) vxorps %xmm0, %xmm0, %xmm0 vmovups %xmm0, -72(%rbp) movq $0, -56(%rbp) movq $10, -104(%rbp) movq (%rsi), %rax movq %rax, -96(%rbp) leaq -104(%rbp), %rax movq %rax, (%rsi) Source line: 3 movq pcre2_default_compile_context_8(%rdi), %rax movq %rax, -56(%rbp) movl $2154391480, %eax # imm = 0x806967B8 vmovq %rax, %xmm0 vpslldq $8, %xmm0, %xmm0 # xmm0 = zero,zero,zero,zero,zero,zero,zero,zero,xmm0[0,1,2,3,4,5,6,7] vmovdqu %xmm0, -80(%rbp) movq %rdi, -64(%rbp) movabsq $jl_apply_generic, %r15 movl $3, %edx movq %r14, %rcx callq *%r15 movq %rax, %rbx movq %rbx, -88(%rbp) movabsq $586874896, %r12 # imm = 0x22FB0010 movq (%r12), %rax testq %rax, %rax jne L198 leaq 98096(%rdi), %rcx movabsq $jl_get_binding_or_error, %rax movl $122868360, %edx # imm = 0x752D288 callq *%rax movq %rax, (%r12) L198: movq 8(%rax), %rax testq %rax, %rax je L263 movq %rax, -80(%rbp) addq $5498232, %rdi # imm = 0x53E578 movq %rdi, -72(%rbp) movq %rbx, -64(%rbp) movq %rax, -56(%rbp) movl $3, %edx movq %r14, %rcx callq *%r15 movq -96(%rbp), %rcx movq %rcx, (%rsi) addq $96, %rsp popq %rbx popq %rdi popq %rsi popq %r12 popq %r14 popq %r15 popq %rbp retq L263: movabsq $jl_undefined_var_error, %rax movl $122868360, %ecx # imm = 0x752D288 callq *%rax ud2 nopw (%rax,%rax) 

Para lenguajes din谩micos sin especializaci贸n de tipo, este c贸digo inflado con todas las instrucciones adicionales es tan bueno como sea posible, por lo que Julia disminuye su velocidad. Para comprender por qu茅 esto es tan importante, tenga en cuenta que cada fragmento de c贸digo que escribe en Julia se compila. Digamos que escribes un bucle en tu script:


 a = 1 for i = 1:100 a += a + f(a) end 

El compilador tendr谩 que compilar este bucle, pero como no puede garantizar que los tipos no cambien, conserva de forma conservadora un pa帽o en todos los tipos, lo que conduce a una ejecuci贸n lenta.


Como evitar un problema


Hay varias formas de evitar este problema. La forma m谩s f谩cil es envolver siempre sus scripts en funciones. Por ejemplo, el c贸digo anterior tomar谩 la forma:


 function geta(a) # can also just define a=1 here for i = 1:100 a += a + f(a) end return a end a = geta(1) 

Esto le dar谩 el mismo resultado, pero dado que el compilador puede especializarse en el tipo a , le proporcionar谩 el c贸digo compilado que desee. Otra cosa que puede hacer es definir sus variables como constantes.


 const b = 5 

Al hacer esto, le dice al compilador que la variable no cambiar谩 y, por lo tanto, podr谩 especializar todo el c贸digo que lo usa en el tipo que es actualmente. Hay una ligera peculiaridad de que Julia realmente le permite cambiar el valor de una constante, pero no un tipo. Por lo tanto, puede usar const para decirle al compilador que no cambiar谩 el tipo. Sin embargo, tenga en cuenta que hay algunas peque帽as peculiaridades:


 const a = 5 f() = a println(f()) # Prints 5 a = 6 println(f()) # Prints 5 # WARNING: redefining constant a 

esto no funciona como se esperaba, porque el compilador, al darse cuenta de que conoce la respuesta a f () = a (ya que a es una constante), simplemente reemplaz贸 la llamada a la funci贸n con la respuesta, dando un comportamiento diferente al de a si no fuera constante.


Moraleja: no escriba sus scripts directamente en REPL, siempre envu茅lvalos en una funci贸n.


Nezhdanchik dos: inestabilidad tipo


Entonces, acabo de expresar una opini贸n sobre la importancia de la especializaci贸n del c贸digo para los tipos de datos. D茅jame hacerte una pregunta, 驴qu茅 sucede cuando tus tipos pueden cambiar? Si adivin贸: "Bueno, en este caso, no puede especializar el c贸digo compilado", tiene raz贸n. Tal problema se conoce como inestabilidad de tipo. Pueden aparecer de manera diferente, pero un ejemplo com煤n es que inicializa el valor con el simple, pero no necesariamente el tipo que deber铆a ser. Por ejemplo, veamos:


 function g() x=1 for i = 1:10 x = x/2 end return x end 

Tenga en cuenta que 1/2 es un n煤mero de coma flotante en Julia. Por lo tanto, si comenzamos con x = 1 , el n煤mero entero cambiar谩 a un n煤mero de coma flotante y, por lo tanto, la funci贸n deber铆a compilar el bucle interno, como si pudiera ser de cualquier tipo. Si en cambio tuvi茅ramos:


 function h() x=1.0 for i = 1:10 x = x/2 end return x end 

entonces toda la funci贸n podr谩 compilarse de manera 贸ptima, sabiendo que x seguir谩 siendo un n煤mero de coma flotante (esta capacidad para que el compilador determine los tipos se llama inferencia de tipos). Podemos verificar el c贸digo compilado para ver la diferencia:


Tela nativa
 pushq %rbp movq %rsp, %rbp pushq %r15 pushq %r14 pushq %r13 pushq %r12 pushq %rsi pushq %rdi pushq %rbx subq $136, %rsp movl $2147565728, %ebx # imm = 0x800140A0 movabsq $jl_get_ptls_states, %rax callq *%rax movq %rax, -152(%rbp) vxorps %xmm0, %xmm0, %xmm0 vmovups %xmm0, -80(%rbp) movq $0, -64(%rbp) vxorps %ymm0, %ymm0, %ymm0 vmovups %ymm0, -128(%rbp) movq $0, -96(%rbp) movq $18, -144(%rbp) movq (%rax), %rcx movq %rcx, -136(%rbp) leaq -144(%rbp), %rcx movq %rcx, (%rax) movq $0, -88(%rbp) Source line: 4 movq %rbx, -104(%rbp) movl $10, %edi leaq 477872(%rbx), %r13 leaq 10039728(%rbx), %r15 leaq 8958904(%rbx), %r14 leaq 64(%rbx), %r12 leaq 10126032(%rbx), %rax movq %rax, -160(%rbp) nopw (%rax,%rax) L176: movq %rbx, -128(%rbp) movq -8(%rbx), %rax andq $-16, %rax movq %r15, %rcx cmpq %r13, %rax je L272 movq %rbx, -96(%rbp) movq -160(%rbp), %rcx cmpq $2147419568, %rax # imm = 0x7FFF05B0 je L272 movq %rbx, -72(%rbp) movq %r14, -80(%rbp) movq %r12, -64(%rbp) movl $3, %edx leaq -80(%rbp), %rcx movabsq $jl_apply_generic, %rax vzeroupper callq *%rax movq %rax, -88(%rbp) jmp L317 nopw %cs:(%rax,%rax) L272: movq %rcx, -120(%rbp) movq %rbx, -72(%rbp) movq %r14, -80(%rbp) movq %r12, -64(%rbp) movl $3, %r8d leaq -80(%rbp), %rdx movabsq $jl_invoke, %rax vzeroupper callq *%rax movq %rax, -112(%rbp) L317: movq (%rax), %rsi movl $1488, %edx # imm = 0x5D0 movl $16, %r8d movq -152(%rbp), %rcx movabsq $jl_gc_pool_alloc, %rax callq *%rax movq %rax, %rbx movq %r13, -8(%rbx) movq %rsi, (%rbx) movq %rbx, -104(%rbp) Source line: 3 addq $-1, %rdi jne L176 Source line: 6 movq -136(%rbp), %rax movq -152(%rbp), %rcx movq %rax, (%rcx) movq %rbx, %rax addq $136, %rsp popq %rbx popq %rdi popq %rsi popq %r12 popq %r13 popq %r14 popq %r15 popq %rbp retq nop 

en contra


Hechizo de ensamblador ordenado
 pushq %rbp movq %rsp, %rbp movabsq $567811336, %rax # imm = 0x21D81D08 Source line: 6 vmovsd (%rax), %xmm0 # xmm0 = mem[0],zero popq %rbp retq nopw %cs:(%rax,%rax) 

隆Qu茅 diferencia en el n煤mero de c谩lculos para obtener el mismo valor!


C贸mo encontrar y lidiar con la inestabilidad de tipos



En este punto, puede preguntar: "Bueno, 驴por qu茅 no usar C para no tener que buscar estas inestabilidades?" La respuesta es:


  1. F谩cil de encontrar
  2. Pueden ser de ayuda.
  3. Puede manejar la inestabilidad con barreras funcionales.


    Julia le da la macro code_warntype para mostrar d贸nde est谩n las inestabilidades de tipo. Por ejemplo, si usamos esto en la funci贸n g que creamos:


     @code_warntype g() 


obtener el an谩lisis
 Variables: #self#::#g x::ANY #temp#@_3::Int64 i::Int64 #temp#@_5::Core.MethodInstance #temp#@_6::Float64 Body: begin x::ANY = 1 # line 3: SSAValue(2) = (Base.select_value)((Base.sle_int)(1,10)::Bool,10,(Base.box)(Int64,(Base.sub_int)(1,1)))::Int64 #temp#@_3::Int64 = 1 5: unless (Base.box)(Base.Bool,(Base.not_int)((#temp#@_3::Int64 === (Base.box)(Int64,(Base.add_int)(SSAValue(2),1)))::Bool)) goto 30 SSAValue(3) = #temp#@_3::Int64 SSAValue(4) = (Base.box)(Int64,(Base.add_int)(#temp#@_3::Int64,1)) i::Int64 = SSAValue(3) #temp#@_3::Int64 = SSAValue(4) # line 4: unless (Core.isa)(x::UNION{FLOAT64,INT64},Float64)::ANY goto 15 #temp#@_5::Core.MethodInstance = MethodInstance for /(::Float64, ::Int64) goto 24 15: unless (Core.isa)(x::UNION{FLOAT64,INT64},Int64)::ANY goto 19 #temp#@_5::Core.MethodInstance = MethodInstance for /(::Int64, ::Int64) goto 24 19: goto 21 21: #temp#@_6::Float64 = (x::UNION{FLOAT64,INT64} / 2)::Float64 goto 26 24: #temp#@_6::Float64 = $(Expr(:invoke, :(#temp#@_5), :(Main./), :(x::Union{Float64,Int64}), 2)) 26: x::ANY = #temp#@_6::Float64 28: goto 5 30: # line 6: return x::UNION{FLOAT64,INT64} end::UNION{FLOAT64,INT64} 

Tenga en cuenta que al principio decimos que el tipo x es Any . Utilizar谩 cualquier tipo que no est茅 designado como strict type , es decir, es un tipo abstracto que necesita ser encuadrado / verificado en cada paso. Vemos que al final devolvemos x como UNION {FLOAT64, INT64} , que es otro tipo no estricto. Esto nos dice que el tipo ha cambiado, causando dificultades. Si en cambio miramos code_warntype para h , obtenemos todos los tipos estrictos:


 @code_warntype h() Variables: #self#::#h x::Float64 #temp#::Int64 i::Int64 Body: begin x::Float64 = 1.0 # line 3: SSAValue(2) = (Base.select_value)((Base.sle_int)(1,10)::Bool,10,(Base.box)(Int64,(Base.sub_int)(1,1)))::Int64 #temp#::Int64 = 1 5: unless (Base.box)(Base.Bool,(Base.not_int)((#temp#::Int64 === (Base.box)(Int64,(Base.add_int)(SSAValue(2),1)))::Bool)) goto 15 SSAValue(3) = #temp#::Int64 SSAValue(4) = (Base.box)(Int64,(Base.add_int)(#temp#::Int64,1)) i::Int64 = SSAValue(3) #temp#::Int64 = SSAValue(4) # line 4: x::Float64 = (Base.box)(Base.Float64,(Base.div_float)(x::Float64,(Base.box)(Float64,(Base.sitofp)(Float64,2)))) 13: goto 5 15: # line 6: return x::Float64 end::Float64 

Esto indica que la funci贸n es de tipo estable y se compilar谩 esencialmente en un c贸digo C 贸ptimo. Por lo tanto, la inestabilidad de tipo no es dif铆cil de encontrar. Lo que es m谩s dif铆cil es encontrar el dise帽o correcto. 驴Por qu茅 resolver la inestabilidad de tipo? Este es un problema de larga data que ha llevado al hecho de que los idiomas escritos din谩micamente dominan el campo de juego de los scripts. La idea es que, en muchos casos, desee encontrar un compromiso entre rendimiento y confiabilidad.


Por ejemplo, puede leer una tabla desde una p谩gina web en la que los enteros se mezclan con n煤meros de coma flotante. En Julia, puede escribir su funci贸n de modo que si todos fueran n煤meros enteros, se compile bien, y si todos fueran n煤meros de coma flotante, tambi茅n se compilar铆a bien. 驴Y si se mezclan? Esto seguir谩 funcionando. Esta es la flexibilidad / conveniencia que conocemos y amamos de un lenguaje como Python / R. Pero Julia le dir谩 directamente (a trav茅s de code_warntype ) cuando sacrifique el rendimiento.


C贸mo lidiar con las inestabilidades de tipo



Hay varias formas de lidiar con las inestabilidades de tipo. En primer lugar, si le gusta algo como C / Fortran donde sus tipos se declaran y no pueden cambiar (lo que garantiza la estabilidad de los tipos), puede hacerlo en Julia:


 local a::Int64 = 5 

Esto hace a n煤mero entero de 64 bits, y si el c贸digo futuro intenta cambiarlo, se generar谩 un mensaje de error (o se realizar谩 la conversi贸n correcta. Pero como la conversi贸n no se redondear谩 autom谩ticamente, lo m谩s probable es que cause errores). Espolv茅alos alrededor de tu c贸digo y obtendr谩s estabilidad de tipo, ala, C / Fortran . Una forma menos complicada de manejar esto es con declaraciones de tipo. Aqu铆 pones la misma sintaxis en el otro lado del signo igual. Por ejemplo:


 a = (b/c)::Float64 

Parece decir: "calcule b / c y aseg煤rese de que la salida sea Float64. Si no lo est谩, intente realizar una conversi贸n autom谩tica. Si la conversi贸n no puede realizarse f谩cilmente, env铆e un error". Colocar tales dise帽os lo ayudar谩 a asegurarse de saber qu茅 tipos est谩n involucrados. Sin embargo, en algunos casos, la inestabilidad de tipo es necesaria. Por ejemplo, supongamos que desea tener un c贸digo confiable, pero el usuario le ofrece algo loco, como:


 arr = Vector{Union{Int64,Float64}}(undef, 4) arr[1]=4 arr[2]=2.0 arr[3]=3.2 arr[4]=1 

que es una matriz de enteros 4x1 y n煤meros de punto flotante. El tipo de elemento real para la matriz es Union {Int64, Float64} , que, como vimos anteriormente, no era estricto, lo que podr铆a generar problemas. El compilador solo sabe que cada valor puede ser un n煤mero entero o un n煤mero de coma flotante, pero no qu茅 elemento de qu茅 tipo. Esto significa que es ingenuo hacer aritm茅tica con esta matriz, por ejemplo:


 function foo{T,N}(array::Array{T,N}) for i in eachindex(array) val = array[i] # do algorithm X on val end end 

ser谩 lento ya que las operaciones ser谩n encuadradas. Sin embargo, podemos utilizar el env铆o m煤ltiple para ejecutar c贸digos de manera especializada. Esto se conoce como el uso de barreras funcionales. Por ejemplo:


 function inner_foo{T<:Number}(val::T) # Do algorithm X on val end function foo2{T,N}(array::Array{T,N}) for i in eachindex(array) inner_foo(array[i]) end end 

Tenga en cuenta que debido al env铆o m煤ltiple, la llamada a inner_foo llama a un m茅todo espec铆ficamente compilado para n煤meros de coma flotante o un m茅todo especialmente compilado para enteros. Por lo tanto, puede poner un c谩lculo largo en inner_foo y a煤n as铆 hacerlo funcionar bien, no inferior al tipo estricto que le proporciona la barrera funcional.


Por lo tanto, espero que vea que Julia ofrece una buena combinaci贸n de rendimiento de escritura fuerte y la comodidad de la escritura din谩mica. Una buena programadora, Julia, tiene ambas opciones para maximizar la productividad y / o la productividad si es necesario.


Sorpresa 3: Eval funciona a nivel mundial



Una de las mayores fortalezas de Julia es su capacidad de metaprogramaci贸n. Esto le permite escribir f谩cilmente programas generadores de c贸digo, reduciendo efectivamente la cantidad de c贸digo que necesita escribir y mantener. Una macro es una funci贸n que se ejecuta en tiempo de compilaci贸n y (generalmente) escupe c贸digo. Por ejemplo:


 macro defa() :(a=5) end 

reemplazar谩 cualquier instancia defa con el c贸digo a = 5 ( :(a = 5) es una expresi贸n entre comillas . El c贸digo de Julia es expresiones y, por lo tanto, la metaprogramaci贸n es una colecci贸n de expresiones).


Puede usar esto para construir cualquier programa complejo de Julia que desee y ponerlo en una funci贸n como un tipo de atajo realmente inteligente. Sin embargo, a veces puede que necesite evaluar directamente el c贸digo generado. Julia le da una funci贸n de eval o macro @eval para hacer esto. En general, debe intentar evitar la eval , pero hay algunos c贸digos donde es necesario, por ejemplo, mi nueva biblioteca para transferir datos entre diferentes procesos para la programaci贸n paralela . , , :


 @eval :(a=5) 

(REPL). , / . Por ejemplo:


 function testeval() @eval :(a=5) return 2a+5 end 

, a REPL. , , :


 function testeval() @eval :(a=5) b = a::Int64 return 2b+5 end 

b 鈥 , , , , , . eval , , REPL .


4:


Julia , . : , .


, ? , , . Por ejemplo:


 a = 2 + 3 + 4 + 5 + 6 + 7 +8 + 9 + 10+ 11+ 12+ 13 a 

, 90, 27. ? a = 2 + 3 + 4 + 5 + 6 + 7 , a = 27 , +8 + 9 + 10+ 11+ 12+ 13 , , , :


 a = 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10+ 11+ 12+ 13 

90, . , .


. 鈥 , . rssdev10

. Por ejemplo:


 x = rand(2,2) a = [cos(2*pi.*x[:,1]).*cos(2*pi.*x[:,2])./(4*pi) -sin(2.*x[:,1]).*sin(2.*x[:,2])./(4)] b = [cos(2*pi.*x[:,1]).*cos(2*pi.*x[:,2])./(4*pi) - sin(2.*x[:,1]).*sin(2.*x[:,2])./(4)] 

, a b 鈥 , ! (2,2) , 鈥 (1-) 2. , , :


 a = [1 -2] b = [1 - 2] 

: 1 -2 . : 1-2 . - . :


 a = [1 2 3 -4 2 -3 1 4] 

2x4. , . : hcat :


 a = hcat(cos(2*pi.*x[:,1]).*cos(2*pi.*x[:,2])./(4*pi),-sin(2.*x[:,1]).*sin(2.*x[:,2])./(4)) 

!


鈩5: ,



(View) 鈥 () , ( ), .

, , . , . , .

鈥 "". "" , . "" 鈥 ( ). ( () ) . , :


 a = [3;4;5] b = a b[1] = 1 

, a[1; 4; 5] , . . b a . , b = a b a . , , b , , ( b a ). , . , , :


 a = rand(2,2) # Makes a random 2x2 matrix b = vec(a) # Makes a view to the 2x2 matrix which is a 1-dimensional array 

b , b a , b . , , ( , ). . , . Por ejemplo:


 c = a[1:2,1] 

( , c a ). , , , , . , , :


 d = @view a[1:2,1] e = view(a,1:2,1) 

d , e 鈥 , d e a , , , . ( , , 鈥 reshape , .) , . Por ejemplo:


 a[1:2,1] = [1;2] 

a , a[1:2,1] view (a, 1:2,1) , , a . -? , :


 b = copy(a) 

, b a , , b a . a , copy! (B, a) , a a ( , b ). . , Vector {Vector} :


 a = [ [1, 2, 3], [4, 5], [6, 7, 8, 9] ] 

. , ?


 b = copy(a) b[1][1] = 10 a 

 3-element Array{Array{Int64,1},1}: [10, 2, 3] [4, 5] [6, 7, 8, 9] 

, a[1][1] 10! ? copy a . a , b , b . , deepcopy :


 b = deepcopy(a) 

, . , , .


鈩6: , In-Place


MATLAB / Python / R . Julia , , , " ". (. . , , , , ). (in-place), . ? in-place ( mutable function ) 鈥 , , . , . , :


 function f() x = [1;5;6] for i = 1:10 x = x + inner(x) end return x end function inner(x) return 2x end 

, inner , , 2x . , . , - y , :


 function f() x = [1;5;6] y = Vector{Int64}(3) for i = 1:10 inner(y,x) for i in 1:3 x[i] = x[i] + y[i] end copy!(y,x) end return x end function inner!(y,x) for i=1:3 y[i] = 2*x[i] end nothing end 

. inner!(y, x) , y . y , y , , , inner! (y, x) . , , mutable (, ""). ! ( ).


, inner!(y, x) . copy!(y, x) 鈥 , x y , . , , . : x y . , x + inner(x) , , , 11 . , .


, , , . - ( loop-fusion ). Julia v0.5 . ( ( broadcast ), ). , f.(x) 鈥 , f x , , . f x , x = x + f. (x) . :


 x .= x .+ f.(x) 

.= , , ,


 for i = 1:length(x) x[i] = x[i] + f(x[i]) end 

, :


 function f() x = [1;5;6] for i = 1:10 x .= x .+ inner.(x) end return x end function inner(x) return 2x end 

MATLAB / R / Python , , , . , , C / Fortran .


: ,


: , . , . , . , , . , .


- , C / Fortran , . - , , !


: ? , . , , ? [ , Javascript var x = 3 x , x = 3 x . ? , - Javascript!]

Source: https://habr.com/ru/post/443484/


All Articles