### Funciones
### Por Arturo Erdely basado en https://docs.julialang.org/en/v1/

function fâ‚�(x, y)
    x + y
end

# o bien

fâ‚‚(x, y) = x + y

println(fâ‚�(2, 3), "\t", fâ‚‚(2, 3))

Î£ = fâ‚‚
println(Î£(3, 4))

# o bien

fâ‚ƒ = (x, y) -> x + y
println(fâ‚ƒ(5, 9))

# o como funciÃ³n anÃ³nima

((x, y)->x+y)(5, 9)


## Uso de `return`

function f(x, y)
    x + y
    x * y
end
println(f(2, 3))

function f(x, y)
    return x + y
    x * y
end
println(f(2, 3))

function f(x, y)
    x + y
    return x * y
end
println(f(2, 3))

function divide(x, y)
    if y == 0.0 && x â‰  0.0
        println("Aviso: OperaciÃ³n en el sistema extendido de numeros reales")
    end
    if y == 0.0 && x == 0.0
        println("Aviso: 0/0 no estÃ¡ definido")
    end
    return x / y
end
println(divide(2, 3))
println(divide(2, 0))
println(divide(0, 0))

g = function(x, y)
    return x + y # prevalece la primera vez que aparece return
    return x * y
end
println(g(2, 3))

# Especificar tipo de dato que entrega una funciÃ³n 

function f(x, y)::Int8
    return x + y
end
println(f(2, 3), "\t typeof: ", typeof(f(2, 3)))

println("Int8 mÃ­n: ", typemin(Int8), "\t mÃ¡x: ", typemax(Int8))
println(f(127, 2), "\t typeof: ", typeof(f(127, 2))) # InexactError
println(f(2.1, 3.6), "\t typeof: ", typeof(f(2.1, 3.6))) # InexactError

# return  nothing  isnothing  Base.notnothing  something 
function f(x, y)
    z = x + y
    println(z)
    return nothing
end
w = f(2, 3)
typeof(w)
println(w)
isnothing(w)
Base.notnothing(5)
Base.notnothing(w)
something(nothing, nothing, 3, nothing, Inf)


## Los operadores son funciones

println(+(1, 2, 3))
println(*(2, 3, 4))


## Operadores implÃ­citos

# [A B C ...]       hcat
# [A; B; C; ...]    vcat
# [A B; C D; ...]   hvcat
# A'                adjoint
# A[i]              getindex
# A[i] = x          setindex!
# A.n               getproperty
# A.n = x           setproperty!

[1 2 3]
hcat(1, 2, 3)

[1; 2; 3]
[1, 2, 3]
vcat(1, 2, 3)


## Funciones anÃ³nimas
# map

println(map(round, [1.2, 3.5, 1.7]))

println(map(x -> x^2 + 2x - 1, [1, 3, -1]))


## Tuplas
# Las tuplas son n-adas (ordenadas) que una vez definidas no pueden modificarse parcialmente,
# a diferencia de los vectores (pero tienen la ventaja de facilitar cÃ¡lculos mÃ¡s rÃ¡pidos)

function que(x)
    println("object: \t", x)
    println("typeof: \t", typeof(x))
    println("sizeof: \t", sizeof(x))
    println("length: \t", length(x))
    return nothing
end
que("Ã³")

x = (1, 1+1)
que(x)

x = (1,)
que(x)

x = (1)
que(x)

x = (0.0, "hello", 6*7)
que(x)
println(x[2])

x = ()
que(x)

x = (3, 9.1, 5//9, 3 - 2im)
x[3]
x[[2, 4]]
x[1:3]
x[1] = 4 # error: no se permite modificar entradas de una tupla
x

# Tuplas con nombres

x = (a = 1, b = 1 + 1)
que(x)
que(x.b)

t = (nombre = "Alicia", edad = 28, covid = false)
t.edad
getproperty(t, :edad) # igual que `t.edad`
t[2]
getindex(t, 2) # igual que `t[2]`
keys(t)
propertynames(t) # igual que `keys(t)`
values(t)
typeof(keys(t))
collect(keys(t))
typeof(values(t))
collect(values(t))

# fusionar tuplas con nombre

x = (a = 1, b = 2, c = 3)
y = (d = 4, e = 5)
merge(x, y)
merge(y, x)

z = (c = 30, d = 40)
merge(x, z)
merge(z, x)

# Respuesta mÃºltiple

function foo(a, b)
    return (a + b, a * b)
end
x = foo(2, 3)
que(x)
y, z = foo(2, 3)
que(z)
(u, v) = foo(2, 3)
que(u)

# intecambiar valores

x = 10
y = 1000
x, y = y, x
(x, y)

# uso de ...

a, b... = [1,2,3,4]
a, b
c, d... = "Â¡Hola!"
c, d

# DescomposiciÃ³n de argumento mÃºltiple

fminmax(x, y) = (y < x) ? (y, x) : (x, y)
rango((minimo, maximo)) = maximo - minimo
que(rango(fminmax(10, 2)))
# aunque ya existen las funciones minmax y range

# NÃºmero variable de argumentos

bar(a, b, x...) = (a, b, x)
que(bar(1, 2))
que(bar(1, 2, 3))
que(bar(1, 2, 3, 4, 5))
que(bar((1,2), 3, 4, (5,6)))

x = (2, 3, 4)
que(bar(1, x))
que(bar(1, x...))
que(bar(x...))
que(bar(x)) # InexactError

x = [1, 2, 3, 4]
que(bar(x...))

baz(a, b) = a + b
args = [1, 2]
que(baz(args...))

# tuple  ntuple

tuple(1, Ï€, 'a')
tuple([], [1,2,3])

fun(x) = x^2 + 1
ntuple(fun, 5)


## Argumentos opcionales

function paciente(name::String = "sin info", age::Int64 = -1, smoker::String = "sin respuesta")
    ficha = (nombre = name, edad = age, fumador = smoker)
    println("nombre: \t", ficha.nombre)
    println("edad: \t\t", ficha.edad)
    println("fumador? \t", ficha.fumador)
    return ficha
end

x = paciente("Juan", 21)
println(typeof(x))
println(x)
println(x.fumador)

y = paciente("Pedro")
println(typeof(y))
println(y)
println(y.nombre)

z = paciente()
println(typeof(z))
println(z)
println(z.nombre)


## Argumentos por nombre

function fichar(nombre::String, edad::Int64; sexo::String = "ND", fuma::Bool = false)
    ficha = (nom = nombre, ed = edad, sex = sexo, fum = fuma)
    println(ficha.nom, " capturado OK")
    return(ficha)
end

x = fichar("Ana", 34, fuma = true)
println(typeof(x))
println(x)
println(x.sex)

# argumentos opcionales por nombre

function alumno(nombre::String, carrera::String; edad = 0)
    return (nombre, carrera, edad)
end
println(alumno("Juan", "ActuarÃ­a"))
println(alumno("MarÃ­a", "MAC", edad = 19))
println(alumno("Pedro", "Historia", 21)) # error, falta especificar `edad`


## Alcance de los valores por defecto

b = 3
function f(x, a = b, b = 1)
    return (x, a, b)
end
println(f(2))
function h(x, b = 1, a = b)
    return(x, a, b)
end
println(h(2))


## Bloques de cÃ³digo con <do>

map(1:10) do x
    println(2x)
end
# es casi lo mismo que lo siguiente, salvo por el formato de salida
println(map(x -> 2x, 1:10))

map(1:10, 11:20) do x,y
    println(x + y)
end


## Vectorizar funciones con <.> (pero en Julia esto no es lo mÃ¡s rÃ¡pido)
#  broadcast

function âˆ (Î¸)
    for k âˆˆ 1:length(Î¸)
        Î¸[k] = sin(Î¸[k])
    end
    Î¸
end

Î¸ = rand(100_000_000)

println("lista comprensiva:")
@time ğŸ˜° = [sin(Î²) for Î² âˆˆ Î¸]

println("vectorizado:")
@time ğŸ˜� = sin.(Î¸)

println("con ciclo <for> puro:")
@time ğŸ˜‚ = âˆ (Î¸)

println(sqrt.([2, 4, 16])) # es lo mismo que:
println(broadcast(sqrt, [2, 4, 16]))

v = [1, 3, 5]
log.(v) .^ 2 .- 1/2 # es lo mismo que:
@. log(v)^2 - 1/2


## ComposiciÃ³n de funciones

Î´(x) = (-x, x)
Î· = Î´ âˆ˜ sqrt âˆ˜ +
typeof(Î·)
println(Î·(2, 3), "\t", âˆš5)


## Cambiar nombres versus mutaciÃ³n

v = [3, 5, 2]
println(sort(v))
println(v)
sort!(v)
println(v)

function setzero!(x)
    x = 0
    return x
end
y = 1
setzero!(y)
println(y) # No funcionÃ³, pero revisa lo que sigue:

function setzero!(x)
    x[1] = 0
    return x
end
y = [1]
setzero!(y)
println(y) # FuncionÃ³ porque los arreglos son mutables


## Pipes

(1, 2) |> sum |> sqrt |> x->x+2
âˆš(1 + 2) + 2


## Definir operadores binarios

#=
Si A es un conjunto, un operador binario cerrado âŠ• en A es
una funciÃ³n âŠ•: A Ã— A â†’ A y se tiene la estructura algebraica (A,âŠ•)
En Julia, como en cualquier otro lenguaje de programaciÃ³n, 
se tienen predefinidos diversos operadores binarios sobre los
distintos conjuntos (tipos) de nÃºmeros. Por ejemplo (Z,+) es
el conjunto de los nÃºmeros enteros Z con la forma usual de
de definir suma * de los mismos:
=#

+(2, 3) 
# o bien:
2 + 3 

#=
AdemÃ¡s de los operadores binarios predefinidos en Julia podemos
definir nuevos, utilizando sÃ­mbolos del siguiente catÃ¡logo:

Con la misma precedencia que la suma usual +
âŠ• âŠ– âŠ� âŠŸ âˆª âˆ¨ âŠ” Â± âˆ“ âˆ” âˆ¸ â‰� âŠ� âŠ» âŠ½ â‹� â‹“ â§º â§» â¨ˆ â¨¢ â¨£ â¨¤ â¨¥ â¨¦ â¨§ â¨¨ 
â¨© â¨ª â¨« â¨¬ â¨­ â¨® â¨¹ â¨º â©� â©‚ â©… â©Š â©Œ â©� â©� â©’ â©” â©– â©— â©› â©� â©¡ â©¢ â©£

Con la misma precedencia que la multiplicaciÃ³n usual *
% Ã— âˆ© âˆ§ âŠ— âŠ˜ âŠ™ âŠš âŠ› âŠ  âŠ¡ âŠ“ âˆ— Â· âˆ¤ â…‹ â‰€ âŠ¼ â‹„ â‹† â‹‡ â‹‰ â‹Š â‹‹ â‹Œ â‹� â‹’ âŸ‘ 
â¦¸ â¦¼ â¦¾ â¦¿ â§¶ â§· â¨‡ â¨° â¨± â¨² â¨³ â¨´ â¨µ â¨¶ â¨· â¨¸ â¨» â¨¼ â¨½ â©€ â©ƒ â©„ â©‹ â©� â©� 
â©‘ â©“ â©• â©˜ â©š â©œ â©� â©Ÿ â©  â«› âŠ� â–· â¨� âŸ• âŸ– âŸ—
=#

âŠ•(x, y) = x + y + 100
âŠ•(1, 2)
1 âŠ• 2

âŠ—(x, y) = x*y - 10
âŠ—(2, 3)

# Para ilustrar la precedencia de operadores:
4 + 7 * 9, (4 + 7)*9, 4 + (7 * 9)
4 âŠ• 7 âŠ— 9, (4 âŠ• 7) âŠ— 9, 4 âŠ• (7 âŠ— 9)


## Otras formas de definir funciones
#  Fuente: https://levelup.gitconnected.com/functional-one-liners-in-julia-e0ed35d4ff7b

# Esto:
3 < 4
# es lo mismo que:
<(3, 4)
# pero si no damos uno de los argumentos:
<(4)
# se crea una funciÃ³n. Por ejemplo:
ff = <(4) 
ff(3)
ff(4)
# que serÃ­a lo mismo que:
gg(x) = (x < 4)
gg(3)
gg(4)
# se puede utilizar para funciones anÃ³nimas:
filter(<(5), 1:10)
filter(>(5), 1:10)
findall(==(4), [4, 8, 4, 2, 1, 5])
findfirst(==(4), [4, 8, 4, 2, 1, 5])
findlast(==("foo"), ["bar", "foo", "qux", "foo"])

# NegaciÃ³n de funciones booleanas
filter(!isempty, ["foo", "", "bar", ""])

# `broadcast` versus `map`

map(sqrt, [9, 16, 25]) 
# es lo mismo que:
broadcast(sqrt, [9, 16, 25])

parse(Int, "42")
map(s -> parse(Int, s), ["7", "42", "1331"])
broadcast(parse, Int, ["7", "42", "1331"])
parse.(Int, ["7", "42", "1331"])
sqrt.(parse.(Int, ["9", "16", "25"])) 

# nesting versus Pipes

string(sqrt(16))
16 |> sqrt |> string

[16, 4, 9] .|> sqrt .|> string
