04 Funkce, metody, makra a metaprogramování
Tento notebook je výukovým materiálem v předmětu BI-JUL.21 vyučovaném v zimním semestru akademického roku 2026/2027 Tomášem Kalvodou. Tvorba těchto materiálů byla podpořena NVS FIT.
Hlavní stránkou předmětu, kde jsou i další notebooky a zajímavé informace, je jeho Course Pages stránka.
versioninfo()Julia Version 1.13.0 Commit d1c37793dd2 (2026-09-09 19:00 UTC) Build Info: Official https://julialang.org release Platform Info: OS: Linux (x86_64-linux-gnu) CPU: 8 × Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz WORD_SIZE: 64 LLVM: libLLVM-20.1.8 (ORCJIT, skylake) GC: Built with stock GC Threads: 1 default, 1 interactive, 1 GC (on 8 virtual cores)
1. Funkce a metody
V předchozí kapitole jsme si ukázali, jak se v Julii pracuje s typy. Asi jste si všimli, že na typy v Julii a jejich hierarchii se lze dívat trochu jako na třídy v jiných programovacích jazycích.
Na rozdíl ale třeba od C++, Javy, Ruby, nebo Pythonu, nejsou instance tříd (objekty) vybaveny metodami. Koncept funkcí a metod je v Julii prakticky zcela oddělen od typů.
1.1 Multiple dispatch
V matematice se jeden symbol používá k označení různých binárních operací. Všechny tyto operace mají jisté společné vlastnosti, které umožňují dívat se na danou operaci jako na sčítání. Tento abstraktní koncept sčítání by v Julii odpovídal funkci. Různé konkrétní způsoby sčítání objektů (nejen) stejných typů (čísla, matice,...) pak v Julii odpovídají metodám.
Jinak řečeno, Julia funkce pod jedním společným jménem sdružuje více metod, které se při volání použijí v závislosti na typech argumentů. Toto paradigma se označuje multiple dispatch (v kontrastu k single dispatch, kde volání je vázáno na konkrétní třídu).
Například zmíněná funkce + má v Julii následující metody (lehce extrémní příklad, ale názorný):
methods(+)- +(x::Bool, z::Complex{Bool}) in Base at complex.jl:301
- +(x::Bool, y::Bool) in Base at bool.jl:169
- +(x::Bool) in Base at bool.jl:166
- +(x::Bool, z::Complex) in Base at complex.jl:308
- +(x::Bool, y::T) where T<:AbstractFloat in Base at bool.jl:176
- +(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat, e::BigFloat) in Base.MPFR at mpfr.jl:765
- +(x::BigFloat, y::BigFloat) in Base.MPFR at mpfr.jl:578
- +(a::BigFloat, b::BigFloat, c::BigFloat) in Base.MPFR at mpfr.jl:752
- +(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat) in Base.MPFR at mpfr.jl:758
- +(x::BigFloat, c::BigInt) in Base.MPFR at mpfr.jl:609
- +(x::BigFloat, c::Union{UInt16, UInt32, UInt64, UInt8}) in Base.MPFR at mpfr.jl:585
- +(x::BigFloat, c::Union{Int16, Int32, Int64, Int8}) in Base.MPFR at mpfr.jl:593
- +(x::BigFloat, c::Union{Float16, Float32, Float64}) in Base.MPFR at mpfr.jl:601
- +(dt::Dates.DateTime, y::Dates.Year) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:25
- +(dt::Dates.DateTime, z::Dates.Month) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:49
- +(x::Dates.DateTime, y::Dates.Quarter) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:77
- +(x::Dates.DateTime, y::Dates.Period) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:83
- +(level::Base.CoreLogging.LogLevel, inc::Integer) in Base.CoreLogging at logging/logging.jl:134
- +(t::Dates.Time, dt::Dates.Date) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:22
- +(x::Dates.Time, y::Dates.TimePeriod) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:85
- +(x::Rational{BigInt}, y::Rational{BigInt}) in Base.GMP.MPQ at gmp.jl:1097
- +(dt::Dates.Date, t::Dates.Time) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:21
- +(dt::Dates.Date, y::Dates.Year) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:29
- +(dt::Dates.Date, z::Dates.Month) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:56
- +(x::Dates.Date, y::Dates.Quarter) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:75
- +(x::Dates.Date, y::Dates.Week) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:79
- +(x::Dates.Date, y::Dates.Day) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:81
- +(B::BitMatrix, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:154
- +(c::BigInt, x::BigFloat) in Base.MPFR at mpfr.jl:614
- +(x::BigInt, y::BigInt) in Base.GMP at gmp.jl:501
- +(a::BigInt, b::BigInt, c::BigInt) in Base.GMP at gmp.jl:541
- +(a::BigInt, b::BigInt, c::BigInt, d::BigInt) in Base.GMP at gmp.jl:542
- +(a::BigInt, b::BigInt, c::BigInt, d::BigInt, e::BigInt) in Base.GMP at gmp.jl:543
- +(x::BigInt, y::BigInt, rest::BigInt...) in Base.GMP at gmp.jl:687
- +(x::BigInt, c::Union{UInt16, UInt32, UInt64, UInt8}) in Base.GMP at gmp.jl:549
- +(x::BigInt, c::Union{Int16, Int32, Int64, Int8}) in Base.GMP at gmp.jl:555
- +(x::Dates.CompoundPeriod, y::Dates.CompoundPeriod) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:335
- +(x::Dates.CompoundPeriod, y::Dates.Period) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:333
- +(x::Dates.CompoundPeriod, y::Dates.TimeType) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:363
- +(z::Complex{Bool}, x::Bool) in Base at complex.jl:302
- +(z::Complex{Bool}, x::Real) in Base at complex.jl:316
- +(::Missing, ::Missing) in Base at missing.jl:119
- +(::Missing) in Base at missing.jl:103
- +(x::Missing, y::Dates.AbstractTime) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:91
- +(::Missing, ::Number) in Base at missing.jl:120
- +(x::Dates.AbstractTime, y::Missing) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:90
- +(r1::OrdinalRange, r2::OrdinalRange) in Base at range.jl:1472
- +(A::Array, Bs::Array...) in Base at arraymath.jl:12
- +(J::LinearAlgebra.UniformScaling, B::BitMatrix) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:155
- +(B::LinearAlgebra.UniformScaling, A::LinearAlgebra.Diagonal) in LinearAlgebra
- +(J1::LinearAlgebra.UniformScaling, J2::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:153
- +(J::LinearAlgebra.UniformScaling, F::LinearAlgebra.Hessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:678
- +(B::LinearAlgebra.UniformScaling, A::LinearAlgebra.Bidiagonal) in LinearAlgebra
- +(J::LinearAlgebra.UniformScaling, x::Number) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:147
- +(x::LinearAlgebra.UniformScaling, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(B::LinearAlgebra.UniformScaling, A::LinearAlgebra.Tridiagonal) in LinearAlgebra
- +(B::LinearAlgebra.UniformScaling, A::LinearAlgebra.SymTridiagonal) in LinearAlgebra
- +(J::LinearAlgebra.UniformScaling, A::AbstractMatrix) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:156
- +(J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:152
- +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:96
- +(x::LinearAlgebra.UnitUpperTriangular, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(UL::LinearAlgebra.UnitUpperTriangular, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:187
- +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1036
- +(A::LinearAlgebra.UnitUpperTriangular, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1028
- +(x::Base.TwicePrecision{T}, y::Base.TwicePrecision{T}) where T in Base at twiceprecision.jl:295
- +(x::Base.TwicePrecision, y::Base.TwicePrecision) in Base at twiceprecision.jl:300
- +(x::Base.TwicePrecision, y::Number) in Base at twiceprecision.jl:289
- +(x::Number, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:148
- +(::Number, ::Missing) in Base at missing.jl:121
- +(x::Number, y::Base.TwicePrecision) in Base at twiceprecision.jl:293
- +(z::Complex) in Base at complex.jl:286
- +(x::Rational) in Base at rational.jl:342
- +(x::Number) in Base at operators.jl:589
- +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:87
- +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigInt) in Base.GMP at gmp.jl:550
- +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) in Base.GMP at gmp.jl:556
- +(a::Integer, b::Integer) in Base at int.jl:1054
- +(x::T, y::T) where T<:Union{Float16, Float32, Float64} in Base at float.jl:492
- +(z::Complex, w::Complex) in Base at complex.jl:288
- +(x::Rational, y::Rational) in Base at rational.jl:356
- +(x::AbstractIrrational, y::AbstractIrrational) in Base at irrationals.jl:199
- +(x::T, y::T) where T<:Number in Base at promotion.jl:627
- +(y::AbstractFloat, x::Bool) in Base at bool.jl:179
- +(z::Complex, x::Bool) in Base at complex.jl:309
- +(x::Real, z::Complex{Bool}) in Base at complex.jl:315
- +(x::Real, z::Complex) in Base at complex.jl:327
- +(z::Complex, x::Real) in Base at complex.jl:328
- +(x::Rational, y::Integer) in Base at rational.jl:385
- +(y::Integer, x::Rational) in Base at rational.jl:392
- +(c::Union{UInt16, UInt32, UInt64, UInt8}, x::BigFloat) in Base.MPFR at mpfr.jl:590
- +(c::Union{Int16, Int32, Int64, Int8}, x::BigFloat) in Base.MPFR at mpfr.jl:598
- +(c::Union{Float16, Float32, Float64}, x::BigFloat) in Base.MPFR at mpfr.jl:606
- +(x::Number, y::Number) in Base at promotion.jl:433
- +(A::LinearAlgebra.UpperHessenberg, B::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:141
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.UpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.Diagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.Tridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(H::LinearAlgebra.UpperHessenberg, x::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:148
- +(r1::StepRangeLen{T, R}, r2::StepRangeLen{T, R}) where {R<:Base.TwicePrecision, T} in Base at twiceprecision.jl:625
- +(r1::StepRangeLen{T, S}, r2::StepRangeLen{T, S}) where {T, S} in Base at range.jl:1495
- +(x::Integer, y::AbstractChar) in Base at char.jl:254
- +(x::Integer, y::Ptr) in Base at pointer.jl:316
- +(x::Ptr, y::Integer) in Base at pointer.jl:314
- +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:96
- +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1024
- +(A::LinearAlgebra.LowerTriangular, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1016
- +(F::LinearAlgebra.Hessenberg, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:677
- +(r::AbstractRange{<:Dates.TimeType}, x::Dates.Period) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/ranges.jl:65
- +(x::T, y::Integer) where T<:AbstractChar in Base at char.jl:244
- +(S::LinearAlgebra.Symmetric, D::LinearAlgebra.Diagonal{var"#s5056", V} where {var"#s5056"<:Number, V<:AbstractVector{var"#s5056"}}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:298
- +(A::LinearAlgebra.Symmetric, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:750
- +(A::LinearAlgebra.Symmetric{var"#s5055", S} where {var"#s5055"<:Real, S<:(AbstractMatrix{<:var"#s5055"})}, B::LinearAlgebra.Hermitian) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:748
- +(A::LinearAlgebra.Symmetric, B::LinearAlgebra.Symmetric) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:739
- +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/tridiag.jl:220
- +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:261
- +(B::LinearAlgebra.SymTridiagonal, A::LinearAlgebra.Bidiagonal) in LinearAlgebra
- +(B::LinearAlgebra.SymTridiagonal, A::LinearAlgebra.Diagonal) in LinearAlgebra
- +(x::LinearAlgebra.SymTridiagonal, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(A::LinearAlgebra.SymTridiagonal{var"#s5054", V} where {var"#s5054"<:Real, V<:AbstractVector{var"#s5054"}}, B::LinearAlgebra.Hermitian) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:751
- +(B::LinearAlgebra.SymTridiagonal, A::LinearAlgebra.Tridiagonal) in LinearAlgebra
- +(A::LinearAlgebra.SymTridiagonal, B::LinearAlgebra.Symmetric) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:749
- +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1020
- +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1012
- +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1040
- +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1032
- +(A::Union{LinearAlgebra.LowerTriangular{T, S}, LinearAlgebra.UnitLowerTriangular{T, S}, LinearAlgebra.UnitUpperTriangular{T, S}, LinearAlgebra.UpperTriangular{T, S}} where {T, S<:AbstractMatrix{T}}, B::Union{LinearAlgebra.LowerTriangular{T, S}, LinearAlgebra.UnitLowerTriangular{T, S}, LinearAlgebra.UnitUpperTriangular{T, S}, LinearAlgebra.UpperTriangular{T, S}} where {T, S<:AbstractMatrix{T}}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1044
- +(A::LinearAlgebra.AbstractTriangular, B::LinearAlgebra.AbstractTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/triangular.jl:1045
- +(a::Dates.TimeType, b::Dates.Period, c::Dates.Period) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:354
- +(a::Dates.TimeType, b::Dates.Period, c::Dates.Period, d::Dates.Period...) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:355
- +(x::Dates.TimeType, y::Dates.CompoundPeriod) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:357
- +(x::Dates.TimeType) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:8
- +(y::Dates.TimeType, x::StridedArray{<:Union{Dates.CompoundPeriod, Dates.Period}}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:18
- +(A::LinearAlgebra.UpperTriangular, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:96
- +(x::LinearAlgebra.UpperTriangular, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(x::Dates.Instant) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:4
- +(index1::CartesianIndex{N}, index2::CartesianIndex{N}) where N in Base.IteratorsMD at multidimensional.jl:129
- +(index::CartesianIndex) in Base.IteratorsMD at multidimensional.jl:126
- +(A::LinearAlgebra.UnitLowerTriangular, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:96
- +(UL::LinearAlgebra.UnitLowerTriangular, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:187
- +(A::BitArray, B::BitArray) in Base at bitarray.jl:1174
- +(Da::LinearAlgebra.Diagonal, Db::LinearAlgebra.Diagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/diagonal.jl:301
- +(D::LinearAlgebra.Diagonal{var"#s5056", V} where {var"#s5056"<:Real, V<:AbstractVector{var"#s5056"}}, H::LinearAlgebra.Hermitian) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:302
- +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:271
- +(B::LinearAlgebra.Diagonal, A::LinearAlgebra.Bidiagonal) in LinearAlgebra
- +(x::LinearAlgebra.Diagonal, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(D::LinearAlgebra.Diagonal{var"#s5056", V} where {var"#s5056"<:Number, V<:AbstractVector{var"#s5056"}}, S::LinearAlgebra.Symmetric) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:294
- +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:211
- +(A::LinearAlgebra.Diagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:183
- +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:256
- +(B::LinearAlgebra.Tridiagonal, A::LinearAlgebra.Bidiagonal) in LinearAlgebra
- +(B::LinearAlgebra.Tridiagonal, A::LinearAlgebra.Diagonal) in LinearAlgebra
- +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:201
- +(x::LinearAlgebra.Tridiagonal, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(A::LinearAlgebra.Tridiagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/tridiag.jl:898
- +(x::P, y::P) where P<:Dates.Period in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:75
- +(x::Dates.Period, y::Dates.Period) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:332
- +(y::Dates.Period, x::Dates.CompoundPeriod) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:334
- +(y::Dates.Period, x::Dates.TimeType) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/arithmetic.jl:87
- +(x::Dates.Period, r::AbstractRange{<:Dates.TimeType}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/ranges.jl:64
- +(r1::LinRange{T}, r2::LinRange{T}) where T in Base at range.jl:1479
- +(H::LinearAlgebra.Hermitian, D::LinearAlgebra.Diagonal{var"#s5056", V} where {var"#s5056"<:Real, V<:AbstractVector{var"#s5056"}}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:306
- +(A::LinearAlgebra.Hermitian, J::LinearAlgebra.UniformScaling{<:Complex}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:201
- +(A::LinearAlgebra.Hermitian, B::LinearAlgebra.SymTridiagonal{var"#s5053", V} where {var"#s5053"<:Real, V<:AbstractVector{var"#s5053"}}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:752
- +(A::LinearAlgebra.Hermitian, B::LinearAlgebra.Symmetric{var"#s5056", S} where {var"#s5056"<:Real, S<:(AbstractMatrix{<:var"#s5056"})}) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:747
- +(A::LinearAlgebra.Hermitian, B::LinearAlgebra.Hermitian) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/symmetric.jl:739
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.UpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:104
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:266
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.LowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:104
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.UnitUpperTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:104
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.UnitLowerTriangular) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:104
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Bidiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/bidiag.jl:446
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Diagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:156
- +(x::LinearAlgebra.Bidiagonal, H::LinearAlgebra.UpperHessenberg) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/hessenberg.jl:149
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.Tridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:226
- +(A::LinearAlgebra.Bidiagonal, B::LinearAlgebra.SymTridiagonal) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/special.jl:241
- +(x::AbstractArray{<:Dates.TimeType}, y::Union{Dates.CompoundPeriod, Dates.Period}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:6
- +(A::Union{LinearAlgebra.Adjoint{T, M}, LinearAlgebra.Transpose{T, M}} where {T, M<:(AbstractMatrix)}, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:220
- +(A::AbstractMatrix, J::LinearAlgebra.UniformScaling) in LinearAlgebra at /usr/share/julia/stdlib/v1.13/LinearAlgebra/src/uniformscaling.jl:231
- +(r1::Union{LinRange, OrdinalRange, StepRangeLen}, r2::Union{LinRange, OrdinalRange, StepRangeLen}) in Base at range.jl:1488
- +(X::StridedArray{<:Union{Dates.CompoundPeriod, Dates.Period}}, Y::StridedArray{<:Union{Dates.CompoundPeriod, Dates.Period}}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:62
- +(A::AbstractArray, B::AbstractArray) in Base at arraymath.jl:6
- +(x::StridedArray{<:Union{Dates.CompoundPeriod, Dates.Period}}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:55
- +(x::AbstractArray{<:Number}) in Base at abstractarraymath.jl:291
- +(x::Union{Dates.CompoundPeriod, Dates.Period}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/periods.jl:343
- +(x::StridedArray{<:Union{Dates.CompoundPeriod, Dates.Period}}, y::Dates.TimeType) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:10
- +(y::Union{Dates.CompoundPeriod, Dates.Period}, x::AbstractArray{<:Dates.TimeType}) in Dates at /usr/share/julia/stdlib/v1.13/Dates/src/deprecated.jl:14
- +(a, b, c, xs...) in Base at operators.jl:653
Každá z těchto 190 možností představuje konkrétní implementaci sčítání pro argumenty uvedené v signatuře. Zvídavý uživatel může jedním prostým kliknutím rovnou nahlédnout zdrojový kód.
Pozor na ne/jednoznačnost!
Při volání metody se Julia snaží vždy vybrat tu "nejspecifičtější" vzhledem k typovému systému. Například:
f(x::Integer) = 2 * x
f(x::Number) = 3 * xf (generic function with 2 methods)
f(1) # 1 "je" jak Integer tak Number a Integer <: Number2
Int64 <: Numbertrue
Int64 <: Integertrue
Integer <: Numbertrue
f(1.0) # 1.0 "je" jen Number3.0
Samozřejmě hrozí nebezpečí nejednoznačnosti. Na takovou situaci nás naštěstí Julia upozorní.
f(x::Float64, y) = x + y
f(x, y::Float64) = x * yf (generic function with 4 methods)
f(1.0, 2)3.0
f(1, 2.0)2.0
@which f(1.0, 2)@which f(1, 2.0)f(1.0, 2.0)MethodError: f(::Float64, ::Float64) is ambiguous.
Candidates:
f(x, y::Float64)
@ Main In[9]:2
f(x::Float64, y)
@ Main In[9]:1
Possible fix, define
f(::Float64, ::Float64)
Stacktrace:
[1] top-level scope
@ In[14]:1
[2] eval(m::Module, e::Any)
@ Core ./boot.jl:489Funkce f má aktuálně čtyři metody:
methods(f)- f(x, y::Float64) in Main at In[9]:2
- f(x::Float64, y) in Main at In[9]:1
- f(x::Integer) in Main at In[3]:1
- f(x::Number) in Main at In[3]:2
Možná oprava spočívá v dodefinování chybějící metody:
f(x::Float64, y::Float64) = x - yf (generic function with 5 methods)
f(1.0, 2.0)-1.0
methods(f)- f(x::Float64, y::Float64) in Main at In[16]:1
- f(x, y::Float64) in Main at In[9]:2
- f(x::Float64, y) in Main at In[9]:1
- f(x::Integer) in Main at In[3]:1
- f(x::Number) in Main at In[3]:2
f(1, 2.0)1.2 Definice metod: parametrické metody
Podobně jako typy mohly mít parametry, mohou mít parametry i metody. Můžeme tak mít různé varianty metody v závislosti na jejich argumentech.
function same_type(x::T, y::T) where {T}
println(T)
return true
end
function same_type(x, y)
return false
endsame_type (generic function with 2 methods)
Díky anotaci se první metoda použije pouze v případě, že jsou argumenty stejného typu. Pokud nejsou, zavolá se druhá "obecná" metoda.
same_type(1, 2)Int64
true
same_type(1.0, 2.0)Float64
true
same_type(1, 2.0)false
same_type(Int64, String)DataType
true
methods(same_type)- same_type(x::T, y::T) where T in Main at In[20]:1
- same_type(x, y) in Main at In[20]:6
Dále můžeme v anotaci omezit i samotný typ T, nebo použít více typů:
function parametric_f(x::T, y::T, z::S) where { T <: Number, S <: AbstractString }
return nothing
endparametric_f (generic function with 1 method)
Tato metoda vyžaduje, aby první dva argumenty byly stejného "číselného" typu T a aby poslední argument byl podtypem typu AbstractString.
1.3 Příklady a cvičení
V předchozí lekci jsme definovali vlastní typ pro racionální čísla. Pojďme nyní zadefinovat sčítání a další operace i pro ně.
"""
MyRational{T <: Integer} <: Number
_My_ **home** made rational number. Kód `MyRational`, LaTeX ``\\frac{1}{2}``.
"""
struct MyRational{T <: Integer} <: Number
num::T
den::T
function MyRational(num::T, den::T) where { T <: Integer }
den == 0 && error("Zero denominator is forbidden by god!")
if den < 0
num *= -1
den *= -1
end
# divide by common factors
common = gcd(num, den)
new{T}(div(num, common), div(den, common))
end
end
MyRational{T}(z::T) where { T <: Integer } = MyRational(z, one(T))MyRational{Int64}(1)Použili jsme ještě jednu novinku a tou je docstring před definicí typu. Tímto způsobem ho můžete použít i před definicí metod, typů, maker. Později k němu můžete přistupovat pomocí integrované nápovědy, nebo ho použít při generování dokumentace (touto problematikou se budeme zabývat později během semestru). Všimněte si, že v docstringu můžeme používat Markdown.
?MyRationalsearch: MyRational Rational Irrational
MyRational{T <: Integer} <: Number
My home made rational number. Kód MyRational, LaTeX .
Pokud chceme definovat novou metodu funkce, která je definována externě (v jiném modulu, o nich později), musíme ji explicitně importovat nebo musíme uvést celé její jméno. První možnost:
p = MyRational(1, 2)
q = MyRational(2, 3)
p + q+ not defined for MyRational{Int64}
Stacktrace:
[1] error(::String, ::String, ::Type)
@ Base ./error.jl:54
[2] no_op_err(name::String, T::Type)
@ Base ./promotion.jl:626
[3] +(x::MyRational{Int64}, y::MyRational{Int64})
@ Base ./promotion.jl:627
[4] top-level scope
@ In[3]:4
[5] eval(m::Module, e::Any)
@ Core ./boot.jl:489import Base.+
+(p::MyRational{T}, q::MyRational{T}) where { T <: Integer } =
MyRational(p.num * q.den + q.num * p.den, p.den * q.den)+ (generic function with 191 methods)
Pojďme ji hned s nadšením otestovat.
p = MyRational(1, 2)
q = MyRational(2, 3)
p + qMyRational{Int64}(7, 6)Toto je sice správný výsledek, ale esteticky není uspokojivý.
Více oku lahodící vypisování našich racionálních čísel můžeme zajistit pomocí přidání metody k funkci show (viz Custom Pretty-printing):
# bez explicitního `import Base.show`
Base.show(io::IO, q::MyRational{T}) where { T <: Integer } =
print(io, q.num, "/", q.den)Potom dostaneme:
p + q7/6
MyRational(2, -3)-2/3
MyRational(2, 1)2/1
Pokud chceme hezký LaTeX výstup, pak musíme dodefinovat následující metodu.
function Base.show(io::IO, ::MIME"text/latex", q::MyRational{T}) where { T <: Integer }
if q.den == one(T)
print(io, "\\begin{equation*}$(q.num)\\end{equation*}")
elseif q.num < zero(T)
print(io, "\\begin{equation*}-\\frac{$(-q.num)}{$(q.den)}\\end{equation*}")
else
print(io, "\\begin{equation*}\\frac{$(q.num)}{$(q.den)}\\end{equation*}")
end
endMyRational(25, 100)MyRational(-24, 40)MyRational(0, 1)MyRational(2, 2)MyRational(-1, 1)Pro úplnost můžeme jednoduše definovat násobení (binární operátor *) a opačný prvek (unární operátor -) a odčítání (binární operátor -).
Všimněte si, jak v definici odčítání -- pěkně v souladu s matematickou definicí -- použijeme pouze sčítání a definici opačného prvku.
import Base.-, Base.*
*(p::MyRational{T}, q::MyRational{T}) where { T <: Integer } = MyRational(p.num * q.num, p.den * q.den)
-(p::MyRational{T}) where { T <: Integer} = MyRational(-one(T), one(T)) * p
# nebo: = MyRational(-p.num, p.den)
-(p::MyRational{T}, q::MyRational{T}) where { T <: Integer } = p + (-q)- (generic function with 196 methods)
Otestujme správnou funkčnost algebraických operací mezi našimi racionálními čísly:
println("p = ", p, ", q = ", q)
p * qp = 1/2, q = 2/3
-pp - qp ^ 5@which p ^ 5p ^ (-2)/ not defined for MyRational{Int64}
Stacktrace:
[1] error(::String, ::String, ::Type)
@ Base ./error.jl:54
[2] no_op_err(name::String, T::Type)
@ Base ./promotion.jl:626
[3] /(x::MyRational{Int64}, y::MyRational{Int64})
@ Base ./promotion.jl:630
[4] inv(x::MyRational{Int64})
@ Base ./number.jl:299
[5] literal_pow(f::typeof(^), x::MyRational{Int64}, ::Val{-2})
@ Base ./intfuncs.jl:483
[6] top-level scope
@ In[32]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:4891 / ppromotion of types Int64 and MyRational{Int64} failed to change any arguments
Stacktrace:
[1] error(::String, ::String, ::String)
@ Base ./error.jl:54
[2] sametype_error(input::Tuple{Int64, MyRational{Int64}})
@ Base ./promotion.jl:428
[3] not_sametype(x::Tuple{Int64, MyRational{Int64}}, y::Tuple{Int64, MyRational{Int64}})
@ Base ./promotion.jl:422
[4] promote
@ ./promotion.jl:405 [inlined]
[5] /(x::Int64, y::MyRational{Int64})
@ Base ./promotion.jl:436
[6] top-level scope
@ In[33]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489Cvičení: Inverze a mocnění v
Naše racionální čísla bychom ještě chtěli umocňovat, i na záporné exponenty (speciálně na , tedy invertovat). Dokážete vyřešit jak na to?
p ^ 3@which p ^ 3p ^ (-1)/ not defined for MyRational{Int64}
Stacktrace:
[1] error(::String, ::String, ::Type)
@ Base ./error.jl:54
[2] no_op_err(name::String, T::Type)
@ Base ./promotion.jl:626
[3] /(x::MyRational{Int64}, y::MyRational{Int64})
@ Base ./promotion.jl:630
[4] inv(x::MyRational{Int64})
@ Base ./number.jl:299
[5] literal_pow(f::typeof(^), x::MyRational{Int64}, ::Val{-1})
@ Base ./intfuncs.jl:483
[6] top-level scope
@ In[36]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489MyRational{T}(n::T) where { T <: Integer } = MyRational(n, one(T))@which one(1)p ^ (-1)/ not defined for MyRational{Int64}
Stacktrace:
[1] error(::String, ::String, ::Type)
@ Base ./error.jl:54
[2] no_op_err(name::String, T::Type)
@ Base ./promotion.jl:626
[3] /(x::MyRational{Int64}, y::MyRational{Int64})
@ Base ./promotion.jl:630
[4] inv(x::MyRational{Int64})
@ Base ./number.jl:299
[5] literal_pow(f::typeof(^), x::MyRational{Int64}, ::Val{-1})
@ Base ./intfuncs.jl:483
[6] top-level scope
@ In[39]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489Base.:/(p::MyRational{T}, q::MyRational{T}) where { T <: Integer } = MyRational(p.num * q.den, p.den * q.num)pp ^ (-1)MyRational(1, 0) / MyRational(0, 1)Zero denominator is forbidden by god! Stacktrace: [1] error(s::String) @ Base ./error.jl:44 [2] MyRational(num::Int64, den::Int64) @ Main ./In[29]:12 [3] top-level scope @ In[43]:1 [4] eval(m::Module, e::Any) @ Core ./boot.jl:489
Base.inv(q::MyRational{T}) where { T <: Integer } = MyRational(q.den, q.num)inv(MyRational(3, 5))-inv(MyRational(-3, 5))p ^ (-2)p ^ (-1)inv(MyRational(0, 1))Zero denominator is forbidden by god!
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:44
[2] MyRational(num::Int64, den::Int64)
@ Main ./In[29]:12
[3] inv(q::MyRational{Int64})
@ Main ./In[44]:1
[4] top-level scope
@ In[49]:1
[5] eval(m::Module, e::Any)
@ Core ./boot.jl:489p1 + ppromotion of types Int64 and MyRational{Int64} failed to change any arguments
Stacktrace:
[1] error(::String, ::String, ::String)
@ Base ./error.jl:54
[2] sametype_error(input::Tuple{Int64, MyRational{Int64}})
@ Base ./promotion.jl:428
[3] not_sametype(x::Tuple{Int64, MyRational{Int64}}, y::Tuple{Int64, MyRational{Int64}})
@ Base ./promotion.jl:422
[4] promote
@ ./promotion.jl:405 [inlined]
[5] +(x::Int64, y::MyRational{Int64})
@ Base ./promotion.jl:433
[6] top-level scope
@ In[51]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489MyRational(1, 2) + MyRational(BigInt(1), BigInt(2))promotion of types MyRational{Int64} and MyRational{BigInt} failed to change any arguments
Stacktrace:
[1] error(::String, ::String, ::String)
@ Base ./error.jl:54
[2] sametype_error(input::Tuple{MyRational{Int64}, MyRational{BigInt}})
@ Base ./promotion.jl:428
[3] not_sametype(x::Tuple{MyRational{Int64}, MyRational{BigInt}}, y::Tuple{MyRational{Int64}, MyRational{BigInt}})
@ Base ./promotion.jl:422
[4] promote
@ ./promotion.jl:405 [inlined]
[5] +(x::MyRational{Int64}, y::MyRational{BigInt})
@ Base ./promotion.jl:433
[6] top-level scope
@ In[52]:1
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489Racionální čísla jsou v Julii k dispozici i bez našeho typu jako typ Rational{T}. Jako konstruktor můžeme využít i dvojité lomítko:
2 // 32//3
4 // 22//1
1 + 2 // 35//3
q = 3 // 23//2
Poznámka: Hezčí výpis.
function Base.show(io::IO, ::MIME"text/latex", q::Rational{T}) where { T <: Integer }
if q.num < 0
print(io, "\\begin{equation*}-\\frac{$(-q.num)}{$(q.den)}\\end{equation*}")
elseif q.num == 0
print(io, "\\begin{equation*}0\\end{equation*}")
else
print(io, "\\begin{equation*}\\frac{$(q.num)}{$(q.den)}\\end{equation*}")
end
end33 // 12Pozor na type piracy: právě jsme přidali metodu
Base.show(..., ::MIME"text/latex", ::Rational), tedy jsme rozšířili chování cizího typu (RationalzBase). Takový zásah mění chování i všem ostatním v programu a obecně se mu vyhýbáme. U vlastních typů (jakoMyRational) je to naopak v pořádku.
2 // 3@which Rational(1, 2)Poznámka: Julia Rational umí vytvořit exaktní reprezentaci strojového čísla (které z definice je vždy racionální číslo):
x = Rational(0.3)@which Rational(0.3)x.num / x.den0.3
Rational(0.3) - 3 // 10Float64(Rational(0.3) - 3 // 10)-1.1102230246251566e-17
Rational(0.5)Cvičení: Komplexní čísla
Vytvořte vlastní typ MyComplex{T} modelující komplexní čísla a vybavte ho standardními metodami sčítání +, odčítání -, násobení *, dělení / a inverze inv.
struct MyComplex{T <: Real} <: Number
re::T
im::T
end
Base.show(io::IO, z::MyComplex{T}) where { T <: Real } =
if z.im >= 0
print(io, z.re, " + ", z.im, "ı")
else
print(io, z.re, " - ", -z.im, "ı")
endz = MyComplex(1, 2)1 + 2ı
z = MyComplex(-1, 2)-1 + 2ı
z = MyComplex(1, -2)1 - 2ı
import Base.*, Base.+, Base.-, Base.inv, Base./
*(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = nothing # FIX ME
+(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = nothing
-(u::MyComplex{T}) where {T} = nothing
-(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = nothing
function inv(u::MyComplex{T}) where {T}
# ...
return nothing
end
/(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = nothingw = MyComplex(3, 4)zz + wz * winv(z) * MyComplex(1.0, -2.0)MyComplex(1.0, -2.0) / MyComplex(1.0, -2.0)z / zCvičení: Modulární multiplikativní grupa
Pro prvočíslo tvoří množina s operací násobení modulo grupu (nula mezi prvky nepatří – nemá inverzi; tzv. modulární multiplikativní grupa, značí se nebo ). O pak mluvíme jako o modulu.
Vytvořte v Julii typ, který bude parametrizovaný typem integeru a modulem , a bude modelovat výše uvedenou strukturu. Vhodně zadefinujte operaci *, inv a zajistěte pěkný výpis objektů tohoto typu.
Definujte metodu modulus vracící modul.
Doplňte následující šablonu:
Poznámka: k výpočtu isprime používáme balíček Primes. Pokud ho ještě nemáte, nainstalujte si ho, např. pomocí balíčkovacího módu, nebo přímo zde pomocí import Pkg; Pkg.add("Primes").
import Base.*, Base.inv, Base.show
using Primes
"""
Modular Multiplicative Group (MMG).
"""
struct MMG{T <: Integer, P} <: Number
value::T
function MMG(value::T, modulus::T) where { T <: Integer }
isprime(modulus) || error("modulus ($modulus) has to be prime!")
my_value = mod(value, modulus)
iszero(my_value) && error("0 is not acceptable!")
new{T, modulus}(my_value)
end
end
modulus(u::MMG{T, P}) where { T <: Integer, P } = P
function *(a::MMG{T, P}, b::MMG{T, P}) where { T <: Integer, P }
MMG(mod(a.value * b.value, P), P)
end
function inv(a::MMG{T, P}) where { T <: Integer, P }
_, u, _ = gcdx(a.value, P)
MMG(mod(u, P), P)
end
show(io::IO, a::MMG) = print(io, "|", a.value, "|_$(modulus(a))")show (generic function with 282 methods)
Následuje několik ukázek použití.
a = MMG(3, 11); b = MMG(5, 11); c = MMG(5, 13);a|3|_11
b|5|_11
c|5|_13
typeof(a)MMG{Int64, 11}modulus(a)11
a * b|4|_11
# toto by šlo ještě vylepšit..., výchozí chování.
b * cpromotion of types MMG{Int64, 11} and MMG{Int64, 13} failed to change any arguments
Stacktrace:
[1] error(::String, ::String, ::String)
@ Base ./error.jl:54
[2] sametype_error(input::Tuple{MMG{Int64, 11}, MMG{Int64, 13}})
@ Base ./promotion.jl:428
[3] not_sametype(x::Tuple{MMG{Int64, 11}, MMG{Int64, 13}}, y::Tuple{MMG{Int64, 11}, MMG{Int64, 13}})
@ Base ./promotion.jl:422
[4] promote
@ ./promotion.jl:405 [inlined]
[5] *(x::MMG{Int64, 11}, y::MMG{Int64, 13})
@ Base ./promotion.jl:434
[6] top-level scope
@ In[81]:2
[7] eval(m::Module, e::Any)
@ Core ./boot.jl:489a|3|_11
inv(a)|4|_11
a * inv(a)|1|_11
c * inv(c)|1|_13
inv(c)|8|_13
c|5|_13
b ^ 123|4|_11
MMG(0, 3)0 is not acceptable! Stacktrace: [1] error(s::String) @ Base ./error.jl:44 [2] MMG(value::Int64, modulus::Int64) @ Main ./In[72]:14 [3] top-level scope @ In[89]:1 [4] eval(m::Module, e::Any) @ Core ./boot.jl:489
1.4 Předávání argumentů metodám
Už víme, jak v definici metody anotovat typy argumentů a návratové hodnoty. Pro připomenutí:
F(x::Int64)::Float64 = x / (abs(x) + 1)F (generic function with 1 method)
F(1)0.5
F(0)0.0
Ovšem:
F("0")MethodError: no method matching F(::String) The function `F` exists, but no method is defined for this combination of argument types. Closest candidates are: F(::Int64) @ Main In[1]:1 Stacktrace: [1] top-level scope @ In[4]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
Asi je jasné, jak definovat metodu s více pozičními argumenty.
V této části lekce si ukážeme, jak argumentům přiřazovat výchozí hodnoty, jak definovat metody s proměnlivým počtem argumentů, či jak definovat metody s argumenty ve tvaru keyword=value, u nichž nezáleží na pořadí.
U všech níže uvedených způsobů předání argumentů lze uvést i typovou anotaci.
Nepovinné argumenty / výchozí hodnoty
V definici funkce lze pomocí operátoru = přiřadit "posledním" (odzadu, jinak by zápis nebyl jednoznačně interpretovatelný) argumentům výchozí hodnoty, které poté při volání není potřeba vypisovat.
Například:
h(x, y=2) = x + yh (generic function with 2 methods)
h(1)3
h(1, 1)2
methods(h)- h(x, y) in Main at In[5]:1
- h(x) in Main at In[5]:1
I v tomto zápisu lze případně anotovat typ proměnné y pomocí ::.
gg(x, y::Int64=4) = x * ygg (generic function with 2 methods)
gg(1, 2.5)MethodError: no method matching gg(::Int64, ::Float64) The function `gg` exists, but no method is defined for this combination of argument types. Closest candidates are: gg(::Any, ::Int64) @ Main In[9]:1 gg(::Any) @ Main In[9]:1 Stacktrace: [1] top-level scope @ In[10]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
ch(x, y=2, z=4) = x ^ (y ^ z)ch (generic function with 3 methods)
methods(ch)- ch(x, y, z) in Main at In[12]:1
- ch(x, y) in Main at In[12]:1
- ch(x) in Main at In[12]:1
ch(2, 2, 3)256
ch(2, 1)2
ch(2)65536
Operátor ..., "varargs"
Metody mohou mít přirozeně více argumentů. Následující funkce má právě tři poziční argumenty:
g(x, y, z) = x + y + zg (generic function with 1 method)
Argumenty jí můžeme předat buď explicitně jeden po jednom, nebo je předat v tuple a použít ... operátor.
g(1, 2, 3)6
t = (1, 2, 3)
g(t...)6
Ale pozor, následující volání selže. g nemá definovánu metodu, která by si poradila s tuplem na vstupu.
g(t)MethodError: no method matching g(::Tuple{Int64, Int64, Int64})
The function `g` exists, but no method is defined for this combination of argument types.
Closest candidates are:
g(::Any, ::Any, ::Any)
@ Main In[17]:1
Stacktrace:
[1] top-level scope
@ In[20]:1
[2] eval(m::Module, e::Any)
@ Core ./boot.jl:489Operátor ... můžeme použít i v definici funkce samotné.
Umožňuje nám pak vytvořit funkci mající měnící se počet argumentů (variable number of arguments, "varargs").
Následující funkce má jeden nebo více pozičních argumentů:
H(x, args...) = println(args)H (generic function with 1 method)
H(1)()
()()
typeof(())Tuple{}(,) # <- blbostParseError: # Error @ In[27]:1:2 (,) # <- blbost #└ ── Expected `)` or `,` Stacktrace: [1] top-level scope @ In[27]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
(1)1
typeof((1))Int64
(1,)(1,)
typeof((1,))Tuple{Int64}H(1, 2)(2,)
H(1, 2, 3)(2, 3)
Zamyslete se nad následujícími třemi ukázkami.
H(1, ("a", "b", "c"))(("a", "b", "c"),)
H(1, ("a", "b", "c")...)("a", "b", "c")
H(1, "a", "b", "c")Typický reprezentant:
println(1, 2, 3, "Ahoj!")123Ahoj!
vcat([1, 2], [3, 4], [4, 5])6-element Vector{Int64}:
1
2
3
4
4
5hcat([1, 2], [3, 4], [4, 5])2×3 Matrix{Int64}:
1 3 4
2 4 5CH(args::Int64...) = 1CH (generic function with 1 method)
CH(1, "a")MethodError: no method matching CH(::Int64, ::String) The function `CH` exists, but no method is defined for this combination of argument types. Closest candidates are: CH(::Int64...) @ Main In[39]:1 Stacktrace: [1] top-level scope @ In[40]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
Argumenty tvaru keyword=value (keyword arguments, "kwargs")
Jakmile počet argumentů přeroste jistou hranici (pro mě někde kolem 3), tak může být vhodné místo pozičního předávání argumentů použít zadávání pomocí klíče a hodnoty u kterých poté nezáleží na pořadí.
Těmto argumentům také lze přiřazovat výchozí hodnoty.
Takovéto argumenty od těch pozičních oddělíme středníkem ; v signatuře funkce:
G(x, y; operator=+) = operator(x, y)G (generic function with 1 method)
G(1, 2)3
G(2, 3, operator=*)6
Lze i se středníkem za pozičními argumenty:
G(2, 3; operator=/)0.6666666666666666
G(2, 3, *)MethodError: no method matching G(::Int64, ::Int64, ::typeof(*)) The function `G` exists, but no method is defined for this combination of argument types. Closest candidates are: G(::Any, ::Any; operator) @ Main In[41]:1 Stacktrace: [1] top-level scope @ In[46]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
U(x; y) = x + yU (generic function with 1 method)
U(1)UndefKeywordError: keyword argument `y` not assigned Stacktrace: [1] U(x::Int64) @ Main ./In[47]:1 [2] top-level scope @ In[48]:1 [3] eval(m::Module, e::Any) @ Core ./boot.jl:489
U(1, y=42)43
U(1, 2)MethodError: no method matching U(::Int64, ::Int64) The function `U` exists, but no method is defined for this combination of argument types. Closest candidates are: U(::Any; y) @ Main In[47]:1 Stacktrace: [1] top-level scope @ In[50]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
Klíči nemusí být přiřazena výchozí hodnota, ale tuto možnost asi často nepoužijeme.
Julia podporuje i neomezený počet těchto argumentů. Uvažme lehce esoterickou funkci s následující signaturou:
function J(args...; kwargs...)
println(typeof(args))
println(typeof(kwargs))
println(length(args))
println(args)
println(kwargs)
endJ (generic function with 1 method)
methods(J)- J(args...; kwargs...) in Main at In[52]:1
J(1, 2, a=1, b="x")Tuple{Int64, Int64}
Base.Pairs{Symbol, Any, Nothing, @NamedTuple{a::Int64, b::String}}
2
(1, 2)
Base.Pairs{Symbol, Any, Nothing, @NamedTuple{a::Int64, b::String}}(:a => 1, :b => "x")
V proměnné kwargs je poté "slovník", kde je pod symbolem odpovídajícím klíči uložena předávaná hodnota.
function kwargs_example(; kwargs...)
println(keys(kwargs))
println(kwargs[:a])
endkwargs_example (generic function with 1 method)
kwargs_example(a=1)(:a,) 1
kwargs_example(b=10)(:b,)
FieldError: type NamedTuple has no field `a`, available fields: `b`
Stacktrace:
[1] getindex
@ ./namedtuple.jl:166 [inlined]
[2] getindex
@ ./iterators.jl:319 [inlined]
[3] kwargs_example(; kwargs::@Kwargs{b::Int64})
@ Main ./In[55]:3
[4] kwcall(::@NamedTuple{b::Int64}, ::typeof(kwargs_example))
@ Main ./In[55]:1
[5] top-level scope
@ In[57]:1
[6] eval(m::Module, e::Any)
@ Core ./boot.jl:489function křoví(x)
return x + 1
endkřoví (generic function with 1 method)
křoví(1)2
Poziční argumenty nelze předávat pomocí jejich názvu (klíče).
křoví(x=1)MethodError: no method matching křoví(; x::Int64) The function `křoví` exists, but no method is defined for this combination of argument types. Closest candidates are: křoví(::Any) got unsupported keyword argument "x" @ Main In[58]:1 Stacktrace: [1] top-level scope @ In[60]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
roští(; x) = 1roští (generic function with 1 method)
roští(x=1)1
1.5 Návratová hodnota
V dosavadních příkladech metod jsme vždy vraceli hodnotu naposledy vyhodnoceného výrazu, často byl dokonce jenom jeden.
V mnoha situacích ale chceme vrátit hodnotu i z jiného místa, než konce těla.
K tomu nepřekvapivě slouží klíčové slovo return.
function func(x::Integer)
# some funny stuff
for j = 1:10
j >= x && return j
end
return 42
end
func(-10)func(3)func(15)Pomocí tuplů můžeme vracet i "více" hodnot.
function func_tuple(x)
return (x, x^2)
endfunc_tuple (generic function with 1 method)
Pak je vhodné výsledek rovnou přiřadit do dvou proměnných:
a, b = func_tuple(10)(10, 100)
a10
b100
Nebo můžeme samozřejmě přijmout celý tuple.
c = func_tuple(10)
c(10, 100)
1.6 Předávání funkcí a do bloky
Funkce jsou v Julii hodnoty jako každé jiné, takže je můžeme předávat jako argumenty (funkce vyššího řádu). Zápis se často zkracuje pomocí do-bloku: funkce předaná jako první argument se zapíše až za voláním.
map(x -> x^2, [1, 2, 3])3-element Vector{Int64}:
1
4
9map([1, 2, 3]) do x
x^2
enddo-blok se hodí především tam, kde je funkce delší nebo kde chceme zdůraznit, co se s daty děje. Formálně jde o totéž jako předat anonymní funkci prvním argumentem:
function apply_twice(f, x)
return f(f(x))
end
apply_twice(x -> x + 1, 3)
apply_twice(3) do x
x + 1
end5
apply_twice(3)MethodError: no method matching apply_twice(::Int64) The function `apply_twice` exists, but no method is defined for this combination of argument types. Closest candidates are: apply_twice(::Any, ::Any) @ Main In[77]:1 Stacktrace: [1] top-level scope @ In[78]:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489
1.7 Volatelné objekty (funktory)
Metody nemusí patřit jen funkcím s vlastním jménem. Definujeme-li pro typ metodu ve tvaru (::NazevTypu)(args...), chová se jeho instance jako funkce – tzv. funktor. Připomeňme, že i konstruktory jsou vlastně jen metody.
struct Scale
factor::Float64
end
(s::Scale)(x) = s.factor * x
double = Scale(2.0)
double(10)20.0
map(Scale(3.0), [1, 2, 3])3-element Vector{Float64}:
3.0
6.0
9.02. Makra a metaprogramování
Pod metaprogramováním máme na mysli schopnost programu generovat, či modifikovat, svůj zdrojový kód. Makra v Julii jsou inspirována Lispem. Nejde jen o pouhé textové transformace jako v případě maker v C/C++. V Julii má programátor přímý přístup k vnitřní reprezentaci zdrojového kódu.
2.1 Výrazy a AST
Ukažme si, jak Julia zpracovává zdrojový kód. Na počátku máme řetězec. Například:
source = "1 + 2""1 + 2"
Co s tímto zdrojovým kódem udělá parser jazyka Julia?
Vytvoří objekt typu Expr:
ex = Meta.parse(source):(1 + 2)
typeof(ex)Expr
Objekt typu Expr obsahuje v zásadě dvě informace:
ex.head # symbol udávající význam:call
typeof(ex.head)Symbol
ex.args # argumenty3-element Vector{Any}:
:+
1
2Přehledně lze tyto informace vypsat pomocí metody dump:
dump(ex)Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 2
Interpretace tohoto příkladu je nasnadě.
Reprezentuje volání (call) metody + s Int64 argumenty 1 a 2.
Tyto výrazy lze přirozeně vytvářet i přímo pomocí konstruktoru Expr.
Za chvilku si ale ukážeme další způsoby, jak výrazy vytvářet, tento by nebyl příliš efektivní.
myex = Expr(:call, :+, 1, 2):(1 + 2)
ex == myextrue
Výrazy dohromady vytváří stromovou strukturu (AST -- abstract syntax tree), lze je do sebe zanořovat:
dump(Meta.parse("(1 + 2) / 3"))A konečně, výrazy můžeme finálně vyhodnotit pomocí metody eval.
dump(ex)Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 2
dump(myex)Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 2
eval(ex)3
eval(myex)3
ex2 = Expr(:call, :neznámá_věc, 1, 2):(neznámá_věc(1, 2))
dump(ex2)Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol neznámá_věc
2: Int64 1
3: Int64 2
eval(ex2)UndefVarError: `neznámá_věc` not defined in `Main` Suggestion: check for spelling errors or missing imports. Stacktrace: [1] top-level scope @ none:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489 [3] top-level scope @ In[96]:1 [4] eval(m::Module, e::Any) @ Core ./boot.jl:489
2.2 Quote a interpolace
Dalším způsobem vytváření objektů typu Expr je quoting (kód v uvozovkách :-)).
Toho lze docílit dvěma způsoby.
Pro menší výrazy se hodí zápis pomocí :, za kterou v závorce uvedeme výraz v Julii:
myex2 = :((1 + 2) / 3):((1 + 2) / 3)
typeof(myex2)Expr
dump(:(f(1 + 1)))Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol f
2: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 1
Proměnné se ve výrazu uloží pod symboly, ne pod svými hodnotami!
x = 1
dump(:(x + 1))Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Symbol x
3: Int64 1
Pokud bychom chtěli použít skutečně hodnotu proměnné, pak k tomu můžeme použít interpolační symbol $:
x = 1
dump(:($x + 1))Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol +
2: Int64 1
3: Int64 1
Pro větší výrazy můžeme použít quote blok.
ex = quote
for j = 1:10
println(j)
end
endquote
#= In[102]:2 =#
for j = 1:10
#= In[102]:3 =#
println(j)
#= In[102]:4 =#
end
endtypeof(ex)Expr
dump(ex)Expr
head: Symbol block
args: Array{Any}((2,))
1: LineNumberNode
line: Int64 2
file: Symbol In[102]
2: Expr
head: Symbol for
args: Array{Any}((2,))
1: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol j
2: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol :
2: Int64 1
3: Int64 10
2: Expr
head: Symbol block
args: Array{Any}((3,))
1: LineNumberNode
line: Int64 3
file: Symbol In[102]
2: Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol println
2: Symbol j
3: LineNumberNode
line: Int64 4
file: Symbol In[102]
eval(ex)1 2 3 4 5 6 7 8 9 10
2.3 Generování kódu: @eval a @generated
Výrazy umíme sestavit a vyhodnotit. To využijeme, když chceme programově generovat metody – nejčastěji pro celou řadu jmen najednou. K tomu slouží makro @eval, což je pohodlnější zápis za eval s citovaným výrazem (a funguje v něm interpolace přes $).
struct Point
x::Float64
y::Float64
end
for f in (:sin, :cos, :exp)
@eval Base.$f(p::Point) = Point($f(p.x), $f(p.y))
end
sin(Point(1.0, 0.0))Point(0.8414709848078965, 0.0)
cos(Point(2.0, -1.0))Point(-0.4161468365471424, 0.5403023058681398)
Silnější nástroj je generovaná funkce (@generated). Její tělo Julia sestaví až při první specializaci metody na konkrétní typy argumentů a udělá to jen jednou pro každou kombinaci typů. Tělo tedy smí pracovat jen s informacemi dostupnými v době kompilace – typicky s typy argumentů, nikoli s jejich hodnotami.
@generated function mysum(x::Tuple)
N = fieldcount(x)
ex = :(x[1])
for i = 2:N
ex = :($ex + x[$i])
end
return ex
end
mysum((1, 2.5, 3))Ačkoli je @generated mocné, obecně platí: dokud to jde, pište obyčejné metody. Generované funkce se hodí hlavně pro výkonnostní optimalizace a musí se psát obezřetně (např. nesmí volat funkce s vedlejšími efekty).
2.4 Makra
Makra přijímají jako argumenty výrazy (Expr), literály nebo symboly a vracejí výraz (Expr).
Makra definujeme pomocí klíčového slova macro.
Ve zdrojovém kódu poté makra používáme s prefixem @.
Makra se aplikují při parsování zdrojového kódu, před jeho odesláním kompilátoru.
Ukažme si nejprve, jak je v AST reprezentováno volání funkce.
f(a, b) = a + b
dump(Meta.parse("f(x, y)"))Následující makro vypíše argumenty volání funkce.
macro show_args(expr::Expr)
println("Arguments: ", expr.args[2:end])
endNapříklad:
@show_args f(1, 2)x = 42; y = 11
@show_args f(x, y)dump(:(f(x, y)))dump(:(f(1, 2.5)))To není přesně to, co bychom asi chtěli (i když...). Pokud chceme vypsat i hodnoty proměnných, musíme trochu zapracovat....
macro show_args(expr::Expr)
println("Arguments: ")
for arg in expr.args[2:end]
if typeof(arg) == Symbol
println(arg, " = ", eval(:($arg)))
else
println(arg)
end
end
end@show_args f(1, 2)@show_args f(x, y)@show_args f(1, x)@show_args f(x, z)Občas pomocí maker chceme modifikovat prostředí, ve kterém se volají. K tomu můžeme použít metodu esc.
V prvním případě je x pouze lokální proměnná a nemá vztah ke "globální" proměnné x.
macro zerox()
return :(x = 0)
end
macro zerox2()
return esc(:(x = 0))
endx = 42@zeroxx@zerox2xPsaní maker nemusí být úplně jednoduché. Více o této problematice se můžete dozvědět v dokumentaci.
Ve zbytku této části si ukážeme některá užitečná makra a zkusíme pár vlastních maker vytvořit. Výčet není zdaleka vyčerpávající a jde spíše o ochutnávku. Některým z těchto partií se ještě budeme věnovat později během semestru.
2.5 Kontrola expanze maker: @macroexpand a @macroexpand1
Při psaní maker je užitečné vidět, co z nich vlastně vznikne. Makro @macroexpand vrátí výsledný výraz po rekurzivní expanzi všech maker, @macroexpand1 expanduje jen jednu (nejvnější) úroveň.
macro twice(ex)
return :($(esc(ex)); $(esc(ex)))
end
macro squared(ex)
return :($(esc(ex))^2)
end@macroexpand1 @twice @squared(1 + 1)@macroexpand @twice @squared(1 + 1)Na prvním výstupu zůstalo @squared ještě neexpandované, druhý výstup už obsahuje plně expandovaný kód.
2.6 @which
Jak již bylo zmíněno, pod jedním symbolem "funkce" se může skrývat mnoho a mnoho metod.
Občas nemusí být úplně jasné, která z nich se vlastně volá.
Makro @which nám umožňuje dohledat o kterou metodu se v konkrétním případě jedná.
@which 2^3@which 2.0^3.0g1(x::Int64) = x + 1
g1(x::Float64) = x - 1g1 (generic function with 2 methods)
@which g1(1.0)@which g1(1)Podobně se chová makro @edit, které rovnou otevře zdrojový kód.
@edit 3^2Vim: Warning: Output is not to a terminal Vim: Warning: Input is not from a terminal
[?25l[?25h
Error detected while processing /home/kalvin/.vimrc: line 31: E492: Not an editor command: Plug 'JuliaEditorSupport/julia-vim' Press ENTER or type command to continue
InterruptException:
Stacktrace:
[1] try_yieldto(undo::typeof(Base.ensure_rescheduled))
@ Base ./task.jl:1172
[2] wait()
@ Base ./task.jl:1299
[3] wait(c::Base.GenericCondition{Base.Threads.SpinLock}; first::Bool)
@ Base ./condition.jl:141
[4] wait
@ ./condition.jl:136 [inlined]
[5] wait(x::Base.Process, syncd::Bool)
@ Base ./process.jl:706
[6] wait
@ ./process.jl:699 [inlined]
[7] success
@ ./process.jl:568 [inlined]
[8] run(::Cmd; wait::Bool)
@ Base ./process.jl:525
[9] run
@ ./process.jl:522 [inlined]
[10] (::InteractiveUtils.var"#3#4"{…})(cmd::Cmd, path::String, line::Int32, column::Int64)
@ InteractiveUtils /usr/share/julia/stdlib/v1.13/InteractiveUtils/src/editless.jl:103
[11] edit(path::String, line::Int32, column::Int64)
@ InteractiveUtils /usr/share/julia/stdlib/v1.13/InteractiveUtils/src/editless.jl:242
[12] edit(path::String, line::Int32)
@ InteractiveUtils /usr/share/julia/stdlib/v1.13/InteractiveUtils/src/editless.jl:232
[13] edit(argtypes::Union{Tuple, Type{<:Tuple}})
@ InteractiveUtils /usr/share/julia/stdlib/v1.13/InteractiveUtils/src/editless.jl:273
[14] top-level scope
@ In[125]:1
[15] macro expansion
@ /usr/share/julia/stdlib/v1.13/InteractiveUtils/src/macros.jl:254 [inlined]
[16] eval(m::Module, e::Any)
@ Core ./boot.jl:489
Some type information was truncated. Use `show(err)` to see complete types.2.7 @debug, @info, @warn, @error
Tato makra slouží k informování uživatele, lze i kontrolovat, na jaké úrovni se logování provádí (k tomu slouží modul Logging, kterému se budeme věnovat při probírání standardní knihovny).
@debug "???"@info "Hi!"[ Info: Hi!
@warn "Beware!"┌ Warning: Beware! └ @ Main In[128]:1
@error "Unable to compute!"┌ Error: Unable to compute! └ @ Main In[129]:1
2.8 Rychlá kontrola a ladění: @assert a @show
@assert ověří podmínku a při jejím nesplnění vyvolá AssertionError. @show vypíše výraz i s jeho hodnotou – hodí se při ladění i při výkladu.
@assert 1 + 1 == 2@show 2 + 2
x = 3
@show x x^2# @assert 1 + 1 == 3 # vyvolalo by AssertionError2.9 @time, @timed a @timev
Pomocí těchto maker můžeme měřit dobu běhu programu.
Jde o jednodušší variantu makra @benchmark z balíčku BenchmarkTools.jl.
Tato tři makra se liší pouze způsobem výstupu.
a = rand(1_000);a[1:10]10-element Vector{Float64}:
0.7437881767476621
0.725196517218191
0.4541780539427138
0.029581780796472223
0.9475250352695385
0.3681260907190166
0.27903650723244056
0.31227638395478596
0.0016994093584039582
0.33270251080558855@time sort(a)0.000025 seconds (9 allocations: 17.898 KiB)
1000-element Vector{Float64}:
0.0006515149116667063
0.0016994093584039582
0.002029974940028656
0.002247879193459812
0.0037765344107584253
0.0038824643192691966
0.005646922395127096
0.006120174879656881
0.007033987051101476
0.007545760114249456
0.007900083748029862
0.008153248119636247
0.00841516861508318
⋮
0.9936788115472469
0.9941333206367915
0.9951558682897199
0.9959542799075708
0.9974727119077195
0.9975569694336669
0.9978273691571466
0.9982848330126755
0.9988343216147826
0.9991325835617975
0.9993081083152998
0.9997820320607193d jako dictionary:
@timed sort(a)(value = [0.0006515149116667063, 0.0016994093584039582, 0.002029974940028656, 0.002247879193459812, 0.0037765344107584253, 0.0038824643192691966, 0.005646922395127096, 0.006120174879656881, 0.007033987051101476, 0.007545760114249456 … 0.9951558682897199, 0.9959542799075708, 0.9974727119077195, 0.9975569694336669, 0.9978273691571466, 0.9982848330126755, 0.9988343216147826, 0.9991325835617975, 0.9993081083152998, 0.9997820320607193], time = 2.4106e-5, bytes = 18328, gctime = 0.0, gcstats = Base.GC_Diff(18328, 3, 0, 6, 0, 0, 0, 0, 0), lock_conflicts = 0, compile_time = 0.0, recompile_time = 0.0)
v jako verbose, čili podrobnější:
@timev sort(a)0.000023 seconds (9 allocations: 17.898 KiB) elapsed time (ns): 22795.0 gc time (ns): 0 bytes allocated: 18328 pool allocs: 6 non-pool GC allocs: 0 malloc() calls: 3 free() calls: 0 minor collections: 0 full collections: 0
1000-element Vector{Float64}:
0.0006515149116667063
0.0016994093584039582
0.002029974940028656
0.002247879193459812
0.0037765344107584253
0.0038824643192691966
0.005646922395127096
0.006120174879656881
0.007033987051101476
0.007545760114249456
0.007900083748029862
0.008153248119636247
0.00841516861508318
⋮
0.9936788115472469
0.9941333206367915
0.9951558682897199
0.9959542799075708
0.9974727119077195
0.9975569694336669
0.9978273691571466
0.9982848330126755
0.9988343216147826
0.9991325835617975
0.9993081083152998
0.99978203206071932.10 @inbounds a @simd
Makro @inbounds "vypne" kontrolu mezí (bounds checking) při indexování polí.
Makro @simd umožňuje kompilátoru větší možnosti optimalizace for cyklu, viz dokumentaci.
a = [1, 2, 3, 4]4-element Vector{Int64}:
1
2
3
4a[2]2
a[5]BoundsError: attempt to access 4-element Vector{Int64} at index [5]
Stacktrace:
[1] throw_boundserror(A::Vector{Int64}, I::Tuple{Int64})
@ Base ./essentials.jl:14
[2] checkbounds
@ ./essentials.jl:389 [inlined]
[3] getindex(A::Vector{Int64}, i::Int64)
@ Base ./essentials.jl:969
[4] top-level scope
@ In[138]:1
[5] eval(m::Module, e::Any)
@ Core ./boot.jl:489using BenchmarkToolsfunction f1(a::Vector{Float64}, n)
val = 0.0
for j = 1:n
val += a[j]
end
return val
end
function f2(a::Vector{Float64}, n)
val = 0.0
@simd for j = 1:n
@inbounds val += a[j]
end
return val
end
function f3(a::Vector{Float64}, n)
val = 0.0
for j = 1:n
@inbounds val += a[j]
end
return val
end
function f4(a::Vector{Float64}, n)
val = 0.0
@simd for j = 1:n
val += a[j]
end
return val
endf4 (generic function with 1 method)
a = rand(10^8);f1(a, 10^6)499986.9829983195
f2(a, 10^6)499986.9829983291
f3(a, 10^6)499986.9829983195
f4(a, 10^6)499986.9829983291
@benchmark f1($a, 10^6)BenchmarkTools.Trial: 3474 samples with 1 evaluation per sample. Range (min … max): 1.206 ms … 3.382 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 1.250 ms ┊ GC (median): 0.00% Time (mean ± σ): 1.438 ms ± 339.215 μs ┊ GC (mean ± σ): 0.00% ± 0.00% █▄▃▂▂▂▁▁▁▁▁▁▁ ▁▁ ▁ ▁ █████████████████████████████▇█▇███▇█▇▇▆▆▇▅▆▆▄▄▅▃▄▂▂▄▅▃▃▄▆▅ █ 1.21 ms Histogram: log(frequency) by time 2.63 ms < Memory estimate: 0 bytes, allocs estimate: 0.
@benchmark f2($a, 10^6)BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample. Range (min … max): 244.809 μs … 2.174 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 315.895 μs ┊ GC (median): 0.00% Time (mean ± σ): 426.422 μs ± 243.198 μs ┊ GC (mean ± σ): 0.00% ± 0.00% ▂▆▇█▆▅▄▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁ ▁▁▁ ▂ ████████████████████████████▇▇▇▆▇▇▇▇▇█████████▇▇▇█▆▇▅▇▆▇▆▆▇▆▆ █ 245 μs Histogram: log(frequency) by time 1.33 ms < Memory estimate: 0 bytes, allocs estimate: 0.
@benchmark f3($a, 10^6)BenchmarkTools.Trial: 3458 samples with 1 evaluation per sample. Range (min … max): 1.205 ms … 3.351 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 1.255 ms ┊ GC (median): 0.00% Time (mean ± σ): 1.444 ms ± 344.118 μs ┊ GC (mean ± σ): 0.00% ± 0.00% █▄▃▃▂▂▁▁▁▁▁▁▁▁ ▁▁▁ ▁▁ ███████████████████████████████▇███▇█▇▆▆▆▆▅▄▄▅▄▄▄▅▂▄▄▅▅▆▅▄▄ █ 1.21 ms Histogram: log(frequency) by time 2.69 ms < Memory estimate: 0 bytes, allocs estimate: 0.
@benchmark f4($a, 10^6)BenchmarkTools.Trial: 9735 samples with 1 evaluation per sample. Range (min … max): 257.421 μs … 3.960 ms ┊ GC (min … max): 0.00% … 0.00% Time (median): 358.121 μs ┊ GC (median): 0.00% Time (mean ± σ): 512.040 μs ± 325.371 μs ┊ GC (mean ± σ): 0.00% ± 0.00% ▄▇██▆▅▄▃▃▃▃▂▃▃▃▃▂▂▂▂▁▁▁▁▁▁▁▂▂▂▁▂▁▁▁ ▁ ▁ ▂ ███████████████████████████████████████████▇██▇▇▇▆▇▇▅▆▆▅▅▃▆▅▅ █ 257 μs Histogram: log(frequency) by time 1.75 ms < Memory estimate: 0 bytes, allocs estimate: 0.
2.11 @test a @testset
Tato makra z modulu Test nám umožňují přehledně testovat náš kód. Pomocí @testset můžeme sdružit více testů dohromady a pojmenovat je (bere řetězec a blok). Druhé makro testuje, zda je výraz pravdivý, nebo nepravdivý.
Podrobněji se testování a modulu Test budeme věnovat v lekci 06.
Například:
using Test
@testset "isodd method" begin
@test isodd(3) == true
@test isodd(2) == false
endDále máme k dispozici @test_throws pro testování vyvolání výjimky.
2.12 @code_native a další introspekce
a.k.a "We need to go deeper..."
Rodina příbuzných maker nám umožňuje nahlédnout do jednotlivých fází zpracování kódu: @code_lowered (snížený IR), @code_typed (typovaný IR), @code_llvm (LLVM IR), @code_native (strojový kód) a @code_warntype (upozornění na typovou nestabilitu).
function g(x::Int64)
return x + 1
endg (generic function with 2 methods)
@code_native g(2) .file "g"
.section .ltext,"axl",@progbits
.globl julia_g_8329 # -- Begin function julia_g_8329
.p2align 4
.type julia_g_8329,@function
julia_g_8329: # @julia_g_8329
; Function Signature: g(Int64)
; ┌ @ In[150]:1 within `g`
# %bb.0: # %top
#DEBUG_VALUE: g:x <- $rdi
push rbp
mov rbp, rsp
; │ @ In[150]:2 within `g`
; │┌ @ int.jl:87 within `+`
lea rax, [rdi + 1]
; │└
pop rbp
ret
.Lfunc_end0:
.size julia_g_8329, .Lfunc_end0-julia_g_8329
; └
# -- End function
.section ".note.GNU-stack","",@progbits
@code_lowered g(2)CodeInfo( 1 ─ %1 = Main.:+ │ %2 = dynamic (%1)(x, 1) └── return %2 )
@code_typed g(2)CodeInfo( 1 ─ %1 = intrinsic Base.add_int(x, 1)::Int64 └── return %1 ) => Int64
@code_warntype g(2)MethodInstance for g(::Int64) from g(x::Int64) @ Main In[150]:1 Arguments #self#::Core.Const(Main.g) x::Int64 Body::Int64 1 ─ %1 = Main.:+::Core.Const(+) │ %2 = (%1)(x, 1)::Int64 └── return %2
2.13 ProgressMeter.jl
using ProgressMeter@showprogress for i in 1:50
sleep(0.1)
end@showprogress dt=1 desc="Computing..." for i in 1:50
sleep(0.1)
end2.14 Struktury s klíčovými argumenty: @kwdef
Ruční psaní konstruktoru se všemi klíčovými argumenty a jejich výchozími hodnotami je zdlouhavé. Makro @kwdef takový konstruktor vygeneruje za nás.
@kwdef struct Config
steps::Int = 100
tol::Float64 = 1e-6
verbose::Bool = false
end
Config()Config(steps=10, verbose=true)Cvičení
Vytvořte makro @dotimes, které zadaný výraz provede několikrát za sebou.
Přesněji @dotimes n body provede body přesně nkrát.
macro dotimes(n, body)
quote
for i = 1:$n
$body
end
end
end@dotimes 2 println("Hi!")body = 1
n = 3
@dotimes 2 println("Hi!")bodynCvičení: generování kódu
Definujme vlastní "číselný" typ:
struct MyNumber <: Number
value::Float64
endVygenerujte kód, který zadefinuje funkce sin, cos, log, exp pro tento typ.
for func in [:sin, :cos, :log, :exp]
eval(:(Base.$func(x::MyNumber) = MyNumber($func(x.value))))
endsin(MyNumber(0.3))cos(MyNumber(0.3))exp(MyNumber(0.3))log(MyNumber(0.3))Cvičení: Sledování průběhu for cyklu
Vytvořte makro, které bude zobrazovat jednoduchý průběh vyhodnocování for cyklu. Tj.
@progress for j = 1:n
# ...
end
bude efektivně
for j = 1:n
# ...
println(j)
end
Případně se můžete pokusit výpis i více zkrášlit.
Zde jde samozřejmě o cvičení práce s makry.
Julia jinak má poměrně excelentní balíček ProgressMeter.jl poskytující přesně tuto funkcionalitu.
macro progress(expr)
push!(expr.args[2].args, :(println(j)))
return expr
end@progress (macro with 1 method)
@progress for j=1:5
sleep(1)
end1 2 3 4 5
Řešení některých příkladů
Inverze.
import Base.inv
inv(p::MyRational{T}) where { T <: Integer } = MyRational(p.den, p.num)inv(p)p^(-3)Komplexní čísla.
import Base.*, Base.+, Base.-, Base.inv, Base./
*(u::MyComplex{S}, v::MyComplex{T}) where {S, T} =
MyComplex(u.re * v.re - u.im * v.im, u.re * v.im + u.im * v.re)
+(u::MyComplex{S}, v::MyComplex{T}) where {S, T} =
MyComplex(u.re + v.re, u.im + v.im)
-(u::MyComplex{T}) where {T} = MyComplex(-u.re, -u.im)
-(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = u + (-v)
function inv(u::MyComplex{T}) where {T}
d = u.re^2 + u.im^2
return MyComplex(u.re / d, -u.im / d)
end
/(u::MyComplex{S}, v::MyComplex{T}) where {S, T} = u * inv(v)z = MyComplex(1, -2)
w = MyComplex(3, 4)
z + w, z * w, z / w, inv(z)Modulární multiplikativní grupa.
import Base.*, Base.inv, Base.show
using Primes
struct MMG{T <: Integer, P} <: Number
value::T
function MMG(value::T, modulus::T) where { T <: Integer }
isprime(modulus) || error("Modulus has to be prime!")
my_value = mod(value, modulus)
iszero(my_value) && error("0 is not acceptable!")
new{T, modulus}(my_value)
end
end
modulus(u::MMG{T, P}) where { T <: Integer, P } = P
function *(a::MMG{T, P}, b::MMG{T, P}) where { T <: Integer, P }
return MMG(mod(a.value * b.value, P), P)
end
function inv(a::MMG{T, P}) where { T <: Integer, P }
# d = u * a + v * P
d, u, v = gcdx(a.value, P)
return MMG(mod(u, P), P)
end
show(io::IO, u::MMG) = print(io, "|", u.value, "|_", modulus(u))Makro dotimes.
macro dotimes(n, body)
quote
for i = 1:$n
$body
end
end
endPrůběh for cyklu.
macro progress(expr)
expr.head === :for || error("@progress očekává `for` cyklus")
var = expr.args[1].args[1] # jméno řídící proměnné, např. :j
body = expr.args[2]
if !(body isa Expr && body.head === :block)
body = Expr(:block, body)
expr.args[2] = body
end
push!(body.args, :(println($var)))
return esc(expr)
end