Functions

Julia'da bir fonksiyon, bir argüman değerleri demetini bir dönüş değerine eşleyen bir nesnedir. Julia fonksiyonları saf matematiksel fonksiyonlar değildir, çünkü programın küresel durumunu değiştirebilir ve ondan etkilenebilirler. Julia'da fonksiyon tanımlamanın temel sözdizimi şudur:

julia> function f(x, y)
           x + y
       end
f (generic function with 1 method)

Bu fonksiyon x ve y adlı iki argümanı kabul eder ve son değerlendirilen ifadenin değerini, yani x + y'yi döndürür.

Julia'da bir fonksiyon tanımlamak için daha kısa bir sözdizimi vardır. Yukarıda gösterilen geleneksel fonksiyon bildirim sözdizimi, aşağıdaki kompakt "atama biçimi" ile eşdeğerdir:

julia> f(x, y) = x + y
f (generic function with 1 method)

In the assignment form, the body of the function must be a single expression, although it can be a compound expression (see Compound Expressions). Short, simple function definitions are common in Julia. The short function syntax is accordingly quite idiomatic, considerably reducing both typing and visual noise.

Bir fonksiyon, geleneksel parantez sözdizimi kullanılarak çağrılır:

julia> f(2, 3)
5

Parantezsiz, f ifadesi fonksiyon nesnesine atıfta bulunur ve diğer değerler gibi taşınabilir:

julia> g = f;

julia> g(2, 3)
5

Değişkenlerde olduğu gibi, Unicode fonksiyon adları için de kullanılabilir:

julia> ∑(x, y) = x + y
∑ (generic function with 1 method)

julia> ∑(2, 3)
5

Argument Passing Behavior

Julia işlev argümanları, "paylaşım yoluyla geçiş" olarak adlandırılan bir geleneği takip eder; bu, değerlerin işlevlere geçildiğinde kopyalanmadığı anlamına gelir. İşlev argümanları, değerleri referans alabilen yeni değişken bağlantıları (yeni "isimler") olarak işlev görür; tıpkı assignments argument_name = argument_value gibi, böylece referans aldıkları nesneler, geçirilen değerlerle özdeştir. Bir işlev içinde yapılan değişiklikler, değiştirilebilir değerler (örneğin Arrayler) üzerinde, çağıran tarafından görülebilir. (Bu, Scheme, çoğu Lisp, Python, Ruby ve Perl gibi diğer dinamik dillerde bulunan aynı davranıştır.)

Örneğin, fonksiyonda

function f(x, y)
    x[1] = 42    # mutates x
    y = 7 + y    # new binding for y, no mutation
    return y
end

x[1] = 42 ifadesi x nesnesini değiştirir ve bu nedenle bu değişiklik, bu argüman için çağıran tarafından geçirilen dizide görünecektir. Öte yandan, y = 7 + y ataması, orijinal nesneyi değiştirmek yerine y adını yeni bir değer olan 7 + y ile ilişkilendirir ve bu nedenle çağıran tarafından geçirilen ilgili argümanı değiştirmez. Bu, f(x, y) çağrıldığında görülebilir:

julia> a = [4, 5, 6]
3-element Vector{Int64}:
 4
 5
 6

julia> b = 3
3

julia> f(a, b) # returns 7 + b == 10
10

julia> a  # a[1] is changed to 42 by f
3-element Vector{Int64}:
 42
  5
  6

julia> b  # not changed
3

Julia'da yaygın bir gelenek olarak (sözdizimsel bir gereklilik olmamakla birlikte), böyle bir fonksiyon typically be named f!(x, y) yerine f(x, y) şeklinde adlandırılır; bu, çağrı noktasında en az bir argümanın (genellikle ilk olan) değiştirildiğini görsel bir hatırlatıcı olarak belirtir.

Shared memory between arguments

Bir mutasyona uğrayan fonksiyonun davranışı, bir mutasyona uğramış argümanın başka bir argümanla bellek paylaşması durumunda, yani aliasing durumunda (örneğin, birinin diğerinin görünümü olduğu durumlarda) beklenmedik olabilir. Fonksiyonun dokümantasyon dizesi, aliasing'in beklenen sonucu ürettiğini açıkça belirtmedikçe, bu tür girdilerde doğru davranışı sağlamak çağıran kişinin sorumluluğundadır.

Argument-type declarations

Fonksiyon argümanlarının türlerini, argüman adının sonuna ::TypeName ekleyerek belirtebilirsiniz, bu da Julia'daki Type Declarations için alışıldık bir yöntemdir. Örneğin, aşağıdaki fonksiyon Fibonacci numbers'i özyinelemeli olarak hesaplar:

fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2)

ve ::Integer spesifikasyonu, n'nin abstract Integer türünün bir alt türü olduğunda yalnızca çağrılabilir olacağı anlamına gelir.

Argument türü bildirimleri genellikle performans üzerinde hiçbir etkiye sahip değildir: hangi argüman türlerinin (varsa) bildirildiğine bakılmaksızın, Julia çağrıcı tarafından geçirilen gerçek argüman türleri için işlevin özel bir versiyonunu derler. Örneğin, fib(1) çağrısı, Int argümanları için özel olarak optimize edilmiş fib'in derlenmesini tetikler; bu, fib(7) veya fib(15) çağrıldığında yeniden kullanılır. (Bir argüman türü bildiriminin ek derleyici özel durumlarını tetikleyebileceği nadir istisnalar vardır; bkz: Be aware of when Julia avoids specializing.) Julia'da argüman türlerini bildirmenin en yaygın nedenleri ise şunlardır:

  • Gönderim: Methods'da açıklandığı gibi, farklı argüman türleri için bir fonksiyonun farklı sürümlerine ("yöntemler") sahip olabilirsiniz; bu durumda hangi argümanların hangi uygulamanın çağrılacağını belirlemek için argüman türleri kullanılır. Örneğin, fib(x::Number) = ... şeklinde, herhangi bir Number türü için çalışan tamamen farklı bir algoritma uygulayabilirsiniz; bunu Binet's formula kullanarak tam sayılar dışındaki değerlere genişletmek için.
  • Doğruluk: Tip bildirimleri, fonksiyonunuzun yalnızca belirli argüman türleri için doğru sonuçlar döndürmesi durumunda faydalı olabilir. Örneğin, argüman türlerini atlayıp fib(n) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2) yazsaydık, fib(1.5) sessizce mantıksız bir cevap olan 1.0 döndürecekti.
  • Açıklık: Tür bildirimleri, beklenen argümanlar hakkında bir belgeleme biçimi olarak hizmet edebilir.

Ancak, argüman türlerini aşırı kısıtlamak yaygın bir hatadır, bu da işlevin uygulanabilirliğini gereksiz yere sınırlayabilir ve beklemediğiniz durumlarda yeniden kullanılmasını engelleyebilir. Örneğin, yukarıdaki fib(n::Integer) işlevi Int argümanları (makine tam sayıları) ve BigInt rastgele hassasiyetli tam sayılar için eşit derecede iyi çalışır (bkz. BigFloats and BigInts), bu özellikle faydalıdır çünkü Fibonacci sayıları üssel olarak hızla büyür ve Int gibi sabit hassasiyetli türlerin herhangi birini hızla taşar (bkz. Overflow behavior). Ancak, işlevimizi fib(n::Int) olarak tanımlasaydık, BigInt uygulaması gereksiz yere engellenmiş olacaktı. Genel olarak, argümanlar için en genel uygulanabilir soyut türleri kullanmalısınız ve şüphe durumunda, argüman türlerini atlayın. Gerekirse daha sonra argüman türü spesifikasyonları ekleyebilirsiniz ve bunları atlayarak performans veya işlevsellikten ödün vermezsiniz.

The return Keyword

Bir fonksiyonun döndürdüğü değer, değerlendirilen son ifadenin değeridir; bu, varsayılan olarak, fonksiyon tanımının gövdesindeki son ifadedir. Önceki bölümdeki örnek fonksiyon f için bu, x + y ifadesinin değeridir. Alternatif olarak, birçok diğer dilde olduğu gibi, return anahtar kelimesi bir fonksiyonun hemen döndürmesini sağlar ve döndürülen değeri sağlayan bir ifade sunar:

function g(x, y)
    return x * y
    x + y
end

Fonksiyon tanımları etkileşimli oturumlara girilebildiğinden, bu tanımları karşılaştırmak kolaydır:

julia> f(x, y) = x + y
f (generic function with 1 method)

julia> function g(x, y)
           return x * y
           x + y
       end
g (generic function with 1 method)

julia> f(2, 3)
5

julia> g(2, 3)
6

Elbette, g gibi tamamen lineer bir fonksiyon gövdesinde return kullanımı anlamsızdır çünkü x + y ifadesi asla değerlendirilmez ve return ifadesini atlayarak fonksiyondaki son ifade olarak x * y yapabiliriz. Ancak diğer kontrol akışlarıyla birlikte return gerçekten faydalıdır. İşte, x ve y uzunluklarına sahip bir dik üçgenin hipotenüs uzunluğunu hesaplayan ve taşmayı önleyen bir fonksiyon:

julia> function hypot(x, y)
           x = abs(x)
           y = abs(y)
           if x > y
               r = y/x
               return x*sqrt(1 + r*r)
           end
           if y == 0
               return zero(x)
           end
           r = x/y
           return y*sqrt(1 + r*r)
       end
hypot (generic function with 1 method)

julia> hypot(3, 4)
5.0

Bu işlevden dönecek üç olası geri dönüş noktası vardır; bu noktalar, x ve y değerlerine bağlı olarak üç farklı ifadenin değerlerini döndürmektedir. Son satırdaki return ifadesi, son ifade olduğu için atlanabilir.

Return type

Bir dönüş türü, :: operatörünü kullanarak fonksiyon bildiriminde belirtilebilir. Bu, dönüş değerini belirtilen türe dönüştürür.

julia> function g(x, y)::Int8
           return x * y
       end;

julia> typeof(g(1, 2))
Int8

Bu fonksiyon, x ve y türlerinden bağımsız olarak her zaman bir Int8 döndürecektir. Dönüş türleri hakkında daha fazla bilgi için Type Declarations'ya bakın.

Dönüş türü bildirimleri Julia'da nadiren kullanılır: genel olarak, Julia'nın derleyicisinin dönüş türünü otomatik olarak çıkarabileceği "tip-istikrarlı" fonksiyonlar yazmalısınız. Daha fazla bilgi için Performance Tips bölümüne bakın.

Returning nothing

Fonksiyonların bir değer döndürmesine gerek yoksa (yalnızca bazı yan etkiler için kullanılan fonksiyonlar), Julia geleneği nothing değerini döndürmektir:

function printx(x)
    println("x = $x")
    return nothing
end

Bu bir konvansiyondur, çünkü nothing bir Julia anahtar kelimesi değil, yalnızca Nothing türünde bir tekil nesnedir. Ayrıca, yukarıdaki printx fonksiyonu örneğinin yapay olduğunu fark edebilirsiniz, çünkü println zaten nothing döndürdüğünden, return satırı gereksizdir.

return hiçbir şey ifadesi için iki olası kısaltılmış form vardır. Bir yandan, return anahtar kelimesi, hiçbir şeyi örtük olarak döndürdüğü için tek başına kullanılabilir. Öte yandan, fonksiyonlar son olarak değerlendirilen ifadelerini örtük olarak döndürdüğünden, hiçbir şey son ifade olduğunda tek başına kullanılabilir. return hiçbir şey ifadesinin return veya hiçbir şey ile karşılaştırıldığında tercih edilmesi, bir kodlama stilidir.

Operators Are Functions

Julia'da, çoğu operatör, özel sözdizimi desteği olan işlevlerdir. (Özel değerlendirme semantiğine sahip operatörler, && ve || gibi, istisnalardır. Bu operatörler, Short-Circuit Evaluation operatörün değerlendirilmesinden önce operandlarının değerlendirilmemesini gerektirdiğinden, işlev olamazlar.) Buna göre, diğer işlevlerde olduğu gibi, parantezli argüman listeleri kullanarak da uygulayabilirsiniz:

julia> 1 + 2 + 3
6

julia> +(1, 2, 3)
6

Infix biçimi, işlev uygulama biçimiyle tam olarak eşdeğerdir - aslında, ilki işlev çağrısını üretmek için içsel olarak ayrıştırılır. Bu aynı zamanda, + ve * gibi operatörleri diğer işlev değerleriyle yapacağınız gibi atayabileceğiniz ve geçirebileceğiniz anlamına gelir:

julia> f = +;

julia> f(1, 2, 3)
6

f adı altında, fonksiyon infiks notasyonu desteklememektedir.

Operators With Special Names

Birkaç özel ifade, belirgin olmayan isimlere sahip fonksiyon çağrılarına karşılık gelir. Bunlar:

ExpressionCalls
[A B C ...]hcat
[A; B; C; ...]vcat
[A B; C D; ...]hvcat
[A; B;; C; D;; ...]hvncat
A'adjoint
A[i]getindex
A[i] = xsetindex!
A.ngetproperty
A.n = xsetproperty!

Not edin ki [A; B;; C; D;; ...] gibi ama iki ardışık ;dan fazla olan ifadeler de hvncat çağrılarına karşılık gelir.

Anonymous Functions

Julia'daki fonksiyonlar first-class objects: değişkenlere atanabilir ve atandıkları değişken üzerinden standart fonksiyon çağrı sözdizimi kullanılarak çağrılabilir. Argüman olarak kullanılabilirler ve değer olarak döndürülebilirler. Ayrıca, bu sözdizimlerinden birini kullanarak isimsiz olarak da oluşturulabilirler:

julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)

julia> function (x)
           x^2 + 2x - 1
       end
#3 (generic function with 1 method)

Her bir ifade, bir argüman x alan ve bu değerdeki polinomun x^2 + 2x - 1 değerini döndüren bir fonksiyon oluşturur. Sonucun genel bir fonksiyon olduğunu, ancak ardışık numaralandırmaya dayalı olarak derleyici tarafından oluşturulmuş bir isimle geldiğini unutmayın.

Anonim fonksiyonların birincil kullanımı, diğer fonksiyonları argüman olarak alan fonksiyonlara geçiş yapmaktır. Klasik bir örnek map'dır; bu, bir fonksiyonu bir dizinin her bir değerine uygular ve sonuç değerlerini içeren yeni bir dizi döndürür:

julia> map(round, [1.2, 3.5, 1.7])
3-element Vector{Float64}:
 1.0
 4.0
 2.0

Bu, map'ya ilk argüman olarak geçirebileceğiniz adlandırılmış bir fonksiyon etkisi varsa iyidir. Ancak, genellikle, kullanıma hazır, adlandırılmış bir fonksiyon mevcut değildir. Bu durumlarda, anonim fonksiyon yapısı, bir isim gerektirmeden tek kullanımlık bir fonksiyon nesnesi oluşturmayı kolaylaştırır:

julia> map(x -> x^2 + 2x - 1, [1, 3, -1])
3-element Vector{Int64}:
  2
 14
 -2

Birden fazla argüman kabul eden anonim bir fonksiyon, (x,y,z)->2x+y-z sözdizimi kullanılarak yazılabilir.

Anonim fonksiyonlar için argüman türü bildirimleri, adlandırılmış fonksiyonlar için olduğu gibi çalışır; örneğin x::Integer->2x. Anonim bir fonksiyonun dönüş türü belirtilemez.

Sıfır argümanlı anonim bir fonksiyon ()->2+2 şeklinde yazılabilir. Argümanı olmayan bir fonksiyon fikri garip gelebilir, ancak bir sonucun önceden hesaplanamayacağı (veya hesaplanmaması gerektiği) durumlarda faydalıdır. Örneğin, Julia'nın mevcut zamanı saniye cinsinden döndüren sıfır argümanlı time fonksiyonu vardır ve bu nedenle seconds = ()->round(Int, time()) ifadesi, bu zamanı en yakın tam sayıya yuvarlayarak döndüren bir anonim fonksiyondur ve seconds değişkenine atanmıştır. Bu anonim fonksiyon her çağrıldığında seconds() mevcut zaman hesaplanacak ve döndürülecektir.

Tuples

Julia'da, işlev argümanları ve dönüş değerleri ile yakından ilişkili olan yerleşik bir veri yapısı olan tuple bulunmaktadır. Tuple, herhangi bir değeri tutabilen ancak değiştirilemeyen (yani değişmez) sabit uzunlukta bir konteynerdir. Tuple'lar virgüller ve parantezler ile oluşturulur ve indeksleme yoluyla erişilebilir:

julia> (1, 1+1)
(1, 2)

julia> (1,)
(1,)

julia> x = (0.0, "hello", 6*7)
(0.0, "hello", 42)

julia> x[2]
"hello"

Bir uzunluk-1 demetinin bir virgül ile yazılması gerektiğini unutmayın, (1,), çünkü (1) sadece parantez içine alınmış bir değerdir. () boş (uzunluk-0) demeti temsil eder.

Named Tuples

Demetlerin bileşenleri isteğe bağlı olarak adlandırılabilir; bu durumda adlandırılmış demet oluşturulur:

julia> x = (a=2, b=1+2)
(a = 2, b = 3)

julia> x[1]
2

julia> x.a
2

Adlandırılmış demetlerin alanlarına, nokta sözdizimi (x.a) kullanarak isimle erişilebilir; bunun yanı sıra normal dizinleme sözdizimi (x[1] veya x[:a]) de kullanılabilir.

Destructuring Assignment and Multiple Return Values

Bir virgülle ayrılmış değişkenler listesi (isteğe bağlı olarak parantez içinde) bir atamanın sol tarafında görünebilir: sağ taraftaki değer, her bir değişkene sırayla atama yapılarak yapılandırılır:

julia> (a, b, c) = 1:3
1:3

julia> b
2

Sağdaki değer bir yineleyici olmalıdır (bkz. Iteration interface) en az soldaki değişken sayısı kadar uzun (yineleyicinin fazla elemanları göz ardı edilir).

Bu, bir tuple veya başka bir iterable değer döndürerek fonksiyonlardan birden fazla değer döndürmek için kullanılabilir. Örneğin, aşağıdaki fonksiyon iki değer döndürmektedir:

julia> function foo(a, b)
           a+b, a*b
       end
foo (generic function with 1 method)

Eğer bunu bir etkileşimli oturumda geri dönüş değerini herhangi bir yere atamadan çağırırsanız, döndürülen demeti göreceksiniz:

julia> foo(2, 3)
(5, 6)

Destructuring atama, her bir değeri bir değişkene çıkarır:

julia> x, y = foo(2, 3)
(5, 6)

julia> x
5

julia> y
6

Başka bir yaygın kullanım, değişkenleri değiştirmektir:

julia> y, x = x, y
(5, 6)

julia> x
6

julia> y
5

Eğer yalnızca yineleyicinin öğelerinin bir alt kümesine ihtiyaç varsa, göz ardı edilen öğeleri yalnızca alt çizgilerden _ oluşan bir değişkene atamak yaygın bir uygulamadır (bu, aksi takdirde geçersiz bir değişken adı olan Allowed Variable Names'e bakın):

julia> _, _, _, d = 1:10
1:10

julia> d
4

Diğer geçerli sol taraf ifadeleri, atama listesinin elemanları olarak kullanılabilir; bu, setindex! veya setproperty! çağıracak veya yinelemeciden bireysel öğeleri ayrıştıracaktır:

julia> X = zeros(3);

julia> X[1], (a, b) = (1, (2, 3))
(1, (2, 3))

julia> X
3-element Vector{Float64}:
 1.0
 0.0
 0.0

julia> a
2

julia> b
3
Julia 1.6

... atama ile Julia 1.6 gerektirir

Eğer atama listesindeki son sembol ... ile sonlandırılmışsa (bu duruma slurping denir), o zaman sağdaki iteratörün kalan elemanlarının bir koleksiyonu veya tembel bir iteratörü atanacaktır:

julia> a, b... = "hello"
"hello"

julia> a
'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)

julia> b
"ello"

julia> a, b... = Iterators.map(abs2, 1:4)
Base.Generator{UnitRange{Int64}, typeof(abs2)}(abs2, 1:4)

julia> a
1

julia> b
Base.Iterators.Rest{Base.Generator{UnitRange{Int64}, typeof(abs2)}, Int64}(Base.Generator{UnitRange{Int64}, typeof(abs2)}(abs2, 1:4), 1)

Base.rest hakkında belirli yineleyicilerin hassas işlenmesi ve özelleştirilmesi için ayrıntılar için bakın.

Julia 1.9

... atama ifadesinin sonu dışında kullanılması Julia 1.9 gerektirir

Atama atma görevlerinde de herhangi bir pozisyonda gerçekleşebilir. Ancak bir koleksiyonun sonunu yudumlamak yerine, bu her zaman hevesli olacaktır.

julia> a, b..., c = 1:5
1:5

julia> a
1

julia> b
3-element Vector{Int64}:
 2
 3
 4

julia> c
5

julia> front..., tail = "Hi!"
"Hi!"

julia> front
"Hi"

julia> tail
'!': ASCII/Unicode U+0021 (category Po: Punctuation, other)

Bu, Base.split_rest fonksiyonu açısından uygulanmıştır.

Not edin ki, değişken sayıda argüman alan fonksiyon tanımlarında, slurping yalnızca son pozisyonda izin verilmektedir. Ancak bu, single argument destructuring için geçerli değildir, çünkü bu yöntem dağıtımını etkilemez:

julia> f(x..., y) = x
ERROR: syntax: invalid "..." on non-final argument
Stacktrace:
[...]

julia> f((x..., y)) = x
f (generic function with 1 method)

julia> f((1, 2, 3))
(1, 2)

Property destructuring

Atama ayırma işlemi, yineleme yerine atamaların sağ tarafında da özellik adlarına göre yapılabilir. Bu, NamedTuples için kullanılan sözdizimini takip eder ve soldaki her değişkene, atamanın sağ tarafındaki aynı ada sahip bir özelliği getproperty kullanarak atayarak çalışır:

julia> (; b, a) = (a=1, b=2, c=3)
(a = 1, b = 2, c = 3)

julia> a
1

julia> b
2

Argument destructuring

Fonksiyon argümanı içinde de parçalama özelliği kullanılabilir. Eğer bir fonksiyon argümanı adı bir demet (örneğin (x, y)) olarak yazılırsa, o zaman (x, y) = argüman ataması sizin için eklenir:

julia> minmax(x, y) = (y < x) ? (y, x) : (x, y)

julia> gap((min, max)) = max - min

julia> gap(minmax(10, 2))
8

gap tanımındaki ekstra parantez setine dikkat edin. O parantezler olmadan, gap iki argümanlı bir fonksiyon olurdu ve bu örnek çalışmazdı.

Benzer şekilde, nesne parçalama işlev argümanları için de kullanılabilir:

julia> foo((; x, y)) = x + y
foo (generic function with 1 method)

julia> foo((x=1, y=2))
3

julia> struct A
           x
           y
       end

julia> foo(A(3, 4))
7

Anonim fonksiyonlar için, tek bir argümanı parçalamak ekstra bir virgül gerektirir:

julia> map(((x, y),) -> x + y, [(1, 2), (3, 4)])
2-element Array{Int64,1}:
 3
 7

Varargs Functions

Genellikle, rastgele sayıda argüman alabilen fonksiyonlar yazmak kullanışlıdır. Bu tür fonksiyonlar geleneksel olarak "varargs" fonksiyonları olarak bilinir; bu, "değişken sayıda argüman" anlamına gelir. Bir varargs fonksiyonu, son konumsal argümandan sonra üç nokta (...) ekleyerek tanımlanabilir:

julia> bar(a, b, x...) = (a, b, x)
bar (generic function with 1 method)

Değişkenler a ve b, her zamanki gibi ilk iki argüman değerine bağlanır ve değişken x, bar fonksiyonuna ilk iki argümandan sonra geçirilen sıfır veya daha fazla değerin iterable koleksiyonuna bağlanır:

julia> bar(1, 2)
(1, 2, ())

julia> bar(1, 2, 3)
(1, 2, (3,))

julia> bar(1, 2, 3, 4)
(1, 2, (3, 4))

julia> bar(1, 2, 3, 4, 5, 6)
(1, 2, (3, 4, 5, 6))

Bu tüm durumlarda, x, bar'a geçirilen son değerlerin bir demetine bağlıdır.

Değişken argüman olarak geçirilen değerlerin sayısını sınırlamak mümkündür; bu, daha sonra Parametrically-constrained Varargs methods içinde tartışılacaktır.

Diğer taraftan, bir iterable koleksiyondaki değerleri bir fonksiyon çağrısına bireysel argümanlar olarak "splat" etmek genellikle kullanışlıdır. Bunu yapmak için, fonksiyon çağrısında da ... kullanılır:

julia> x = (3, 4)
(3, 4)

julia> bar(1, 2, x...)
(1, 2, (3, 4))

Bu durumda, bir değerler demeti, değişken sayıda argümanın gittiği yere tam olarak bir varargs çağrısına eklenir. Ancak bu durum geçerli olmak zorunda değildir:

julia> x = (2, 3, 4)
(2, 3, 4)

julia> bar(1, x...)
(1, 2, (3, 4))

julia> x = (1, 2, 3, 4)
(1, 2, 3, 4)

julia> bar(x...)
(1, 2, (3, 4))

Ayrıca, bir işlev çağrısına yayılmış olan iterable nesnesi bir demet olmak zorunda değildir:

julia> x = [3, 4]
2-element Vector{Int64}:
 3
 4

julia> bar(1, 2, x...)
(1, 2, (3, 4))

julia> x = [1, 2, 3, 4]
4-element Vector{Int64}:
 1
 2
 3
 4

julia> bar(x...)
(1, 2, (3, 4))

Ayrıca, argümanların yayılacağı fonksiyonun bir varargs fonksiyonu olması gerekmez (ancak genellikle öyledir):

julia> baz(a, b) = a + b;

julia> args = [1, 2]
2-element Vector{Int64}:
 1
 2

julia> baz(args...)
3

julia> args = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> baz(args...)
ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
The function `baz` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  baz(::Any, ::Any)
   @ Main none:1

Stacktrace:
[...]

Gördüğünüz gibi, eğer patlatılmış konteynerde yanlış sayıda eleman varsa, o zaman fonksiyon çağrısı başarısız olacaktır, tıpkı çok fazla argüman açıkça verildiğinde olduğu gibi.

Optional Arguments

Fonksiyon argümanları için mantıklı varsayılan değerler sağlamak genellikle mümkündür. Bu, kullanıcıların her çağrıda her argümanı geçmek zorunda kalmalarını önleyebilir. Örneğin, Date(y, [m, d]) fonksiyonu Dates modülünden, verilen bir yıl y, ay m ve gün d için bir Date türü oluşturur. Ancak, m ve d argümanları isteğe bağlıdır ve varsayılan değerleri 1'dir. Bu davranış kısaca şöyle ifade edilebilir:

julia> using Dates

julia> function date(y::Int64, m::Int64=1, d::Int64=1)
           err = Dates.validargs(Date, y, m, d)
           err === nothing || throw(err)
           return Date(Dates.UTD(Dates.totaldays(y, m, d)))
       end
date (generic function with 3 methods)

Gözlemleyin ki, bu tanım UTInstant{Day} türünde bir argüman alan Date fonksiyonunun başka bir yöntemini çağırmaktadır.

Bu tanım ile, fonksiyon ya bir, ya iki ya da üç argüman ile çağrılabilir ve yalnızca bir veya iki argüman belirtildiğinde 1 otomatik olarak geçilir:

julia> date(2000, 12, 12)
2000-12-12

julia> date(2000, 12)
2000-12-01

julia> date(2000)
2000-01-01

Opsiyonel argümanlar, aslında farklı sayıda argümanla birden fazla yöntem tanımı yazmak için kullanışlı bir sözdizimidir (bkz. Note on Optional and keyword Arguments). Bu, date fonksiyonu örneğimiz için methods fonksiyonunu çağırarak kontrol edilebilir:

julia> methods(date)
# 3 methods for generic function "date":
[1] date(y::Int64) in Main at REPL[1]:1
[2] date(y::Int64, m::Int64) in Main at REPL[1]:1
[3] date(y::Int64, m::Int64, d::Int64) in Main at REPL[1]:1

Keyword Arguments

Bazı fonksiyonlar büyük sayıda argümana ihtiyaç duyar veya çok sayıda davranışa sahiptir. Bu tür fonksiyonları nasıl çağıracağınızı hatırlamak zor olabilir. Anahtar kelime argümanları, argümanların yalnızca konumla değil, adla tanımlanmasına izin vererek bu karmaşık arayüzlerin kullanılmasını ve genişletilmesini kolaylaştırabilir.

Örneğin, bir çizgi çizen plot adlı bir fonksiyonu düşünün. Bu fonksiyon, çizgi stili, genişliği, rengi ve benzeri şeyleri kontrol etmek için birçok seçeneğe sahip olabilir. Eğer anahtar kelime argümanlarını kabul ediyorsa, olası bir çağrı şöyle görünebilir: plot(x, y, width=2), burada yalnızca çizgi genişliğini belirtmeyi seçmişiz. Bu durumun iki amacı vardır. Çağrı daha okunabilir hale gelir, çünkü bir argümanı anlamıyla etiketleyebiliriz. Ayrıca, büyük sayıda argümanın herhangi bir alt kümesini, herhangi bir sırayla geçmek mümkün hale gelir.

Anahtar kelime argümanları ile fonksiyonlar, imzada bir noktalı virgül kullanılarak tanımlanır:

function plot(x, y; style="solid", width=1, color="black")
    ###
end

Fonksiyon çağrıldığında, noktalı virgül isteğe bağlıdır: plot(x, y, width=2) veya plot(x, y; width=2) şeklinde çağrılabilir, ancak birinci stil daha yaygındır. Aşağıda açıklandığı gibi, yalnızca varargs veya hesaplanan anahtar kelimeleri geçerken açık bir noktalı virgül gereklidir.

Anahtar argüman varsayılan değerleri yalnızca gerekli olduğunda (karşılık gelen bir anahtar argüman geçilmediğinde) ve soldan sağa doğru değerlendirilir. Bu nedenle varsayılan ifadeler önceki anahtar argümanlara atıfta bulunabilir.

Anahtar argüman türleri aşağıdaki gibi açıkça belirtilebilir:

function f(; x::Int=1)
    ###
end

Anahtar argümanlar, varargs fonksiyonlarında da kullanılabilir:

function plot(x...; style="solid")
    ###
end

Ekstra anahtar kelime argümanları ... kullanılarak toplanabilir, varargs fonksiyonlarında olduğu gibi:

function f(x; y=0, kwargs...)
    ###
end

f içinde, kwargs adlandırılmış bir demet üzerinde değiştirilemez bir anahtar-değer yineleyicisidir. Adlandırılmış demetler (ve Symbol anahtarlarına sahip sözlükler ile ilk değerleri sembol olan iki değerli koleksiyonlar üreten diğer yineleyiciler) bir çağrıda anahtar kelime argümanları olarak noktalı virgül kullanılarak geçirilebilir, örneğin f(x, z=1; kwargs...).

Eğer bir anahtar kelime argümanı yöntem tanımında varsayılan bir değere atanmadıysa, o zaman zorunludur: çağıran bir değer atamazsa UndefKeywordError istisnası fırlatılacaktır:

function f(x; y)
    ###
end
f(3, y=5) # ok, y is assigned
f(3)      # throws UndefKeywordError(:y)

Bir nokta da noktalı virgülden sonra anahtar => değer ifadeleri geçirebilir. Örneğin, plot(x, y; :width => 2) ifadesi plot(x, y, width=2) ile eşdeğerdir. Bu, anahtar adının çalışma zamanında hesaplandığı durumlarda faydalıdır.

Bir çıplak tanımlayıcı veya nokta ifadesi bir noktalı virgülden sonra meydana geldiğinde, anahtar kelime argüman adı tanımlayıcı veya alan adı ile ima edilir. Örneğin plot(x, y; width) ifadesi plot(x, y; width=width) ile eşdeğerdir ve plot(x, y; options.width) ifadesi plot(x, y; width=options.width) ile eşdeğerdir.

Anahtar argümanların doğası, aynı argümanı birden fazla kez belirtmeyi mümkün kılar. Örneğin, plot(x, y; options..., width=2) çağrısında options yapısının da width için bir değer içermesi mümkündür. Bu durumda, en sağdaki örnek önceliğe sahiptir; bu örnekte, width kesinlikle 2 değerine sahip olacaktır. Ancak, aynı anahtar argümanı birden fazla kez açıkça belirtmek, örneğin plot(x, y, width=2, width=3), izin verilmez ve bir sözdizimi hatasına yol açar.

Evaluation Scope of Default Values

Seçenekli ve anahtar kelime argüman varsayılan ifadeleri değerlendirildiğinde, yalnızca önceki argümanlar kapsamda bulunur. Örneğin, bu tanım verildiğinde:

function f(x, a=b, b=1)
    ###
end

a=b ifadesindeki b, dış kapsamda bulunan bir b'ye atıfta bulunur, sonraki argüman b'ye değil.

Do-Block Syntax for Function Arguments

Fonksiyonları diğer fonksiyonlara argüman olarak geçirmek güçlü bir tekniktir, ancak bunun için kullanılan sözdizimi her zaman pratik değildir. Bu tür çağrılar, fonksiyon argümanının birden fazla satır gerektirdiği durumlarda özellikle yazması zor hale gelir. Örneğin, map fonksiyonunu birkaç durumu olan bir fonksiyona çağırmayı düşünün:

map(x->begin
           if x < 0 && iseven(x)
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end,
    [A, B, C])

Julia, bu kodu daha net bir şekilde yeniden yazmak için do adlı bir ayrılmış kelime sağlar:

map([A, B, C]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end

do x sözdizimi, x argümanına sahip bir anonim fonksiyon oluşturur ve anonim fonksiyonu "dış" fonksiyona ilk argüman olarak geçirir - bu örnekte map. Benzer şekilde, do a,b iki argümanlı bir anonim fonksiyon oluşturur. do (a,b) ifadesinin bir argümanlı bir anonim fonksiyon oluşturduğunu, argümanının bir demet olduğunu unutmayın. Sade bir do, ardından gelenin () -> ... biçiminde bir anonim fonksiyon olduğunu belirtir.

Bu argümanların nasıl başlatıldığı "dış" fonksiyona bağlıdır; burada, map sırasıyla x'i A, B, C olarak ayarlayacak ve her birinde anonim fonksiyonu çağıracaktır, tıpkı map(func, [A, B, C]) sözdiziminde olduğu gibi.

Bu sözdizimi, işlevleri etkili bir şekilde dilin genişletilmesi için kullanmayı kolaylaştırır, çünkü çağrılar normal kod blokları gibi görünür. map gibi sistem durumunu yönetmek gibi oldukça farklı birçok olası kullanım vardır. Örneğin, açılan dosyanın sonunda kapatılmasını sağlayan bir open sürümü vardır:

open("outfile", "w") do io
    write(io, data)
end

Bu, aşağıdaki tanım ile gerçekleştirilir:

function open(f::Function, args...)
    io = open(args...)
    try
        f(io)
    finally
        close(io)
    end
end

Burada, open önce dosyayı yazma için açar ve ardından elde edilen çıktı akışını do ... end bloğunda tanımladığınız anonim fonksiyona geçirir. Fonksiyonunuzdan çıkıldığında, 4d61726b646f776e2e436f64652822222c20226f70656e2229_40726566 akışın düzgün bir şekilde kapatılmasını sağlar; bu, fonksiyonunuzun normal bir şekilde çıkıp çıkmadığına veya bir istisna fırlatıp fırlatmadığına bakılmaksızın gerçekleşir. (try/finally yapısı Control Flow'da açıklanacaktır.)

do bloğu sözdizimi ile, kullanıcı fonksiyonunun argümanlarının nasıl başlatıldığını bilmek için belgeleri veya uygulamayı kontrol etmek faydalıdır.

Bir do bloğu, diğer iç işlevler gibi, kapsayıcı kapsamından değişkenleri "yakalayabilir". Örneğin, open...do örneğinde yukarıda belirtilen data değişkeni dış kapsamdan yakalanır. Yakalanan değişkenler, performance tips üzerinde tartışıldığı gibi performans zorlukları yaratabilir.

Function composition and piping

Julia'da fonksiyonlar, birleştirilerek veya boru hattı (zincirleme) ile bir araya getirilebilir.

Fonksiyon bileşimi, fonksiyonları bir araya getirip elde edilen bileşimi argümanlara uyguladığınızda ortaya çıkar. Fonksiyon bileşimi operatörünü () kullanarak fonksiyonları birleştirirsiniz, bu nedenle (f ∘ g)(args...; kw...) ifadesi f(g(args...; kw...)) ile aynıdır.

REPL'de ve uygun şekilde yapılandırılmış editörlerde bileşim operatörünü \circ<tab> yazarak yazabilirsiniz.

Örneğin, sqrt ve + fonksiyonları şu şekilde birleştirilebilir:

julia> (sqrt ∘ +)(3, 6)
3.0

Bu önce sayıları toplar, ardından sonucun karekökünü bulur.

Sonraki örnek, üç fonksiyonu birleştirir ve sonucu bir dizi dize üzerinde haritalar:

julia> map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
6-element Vector{Char}:
 'U': ASCII/Unicode U+0055 (category Lu: Letter, uppercase)
 'N': ASCII/Unicode U+004E (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

Fonksiyon zincirleme (bazen "borulama" veya verileri bir sonraki fonksiyona göndermek için "bir boru kullanma" olarak adlandırılır) bir fonksiyonu önceki fonksiyonun çıktısına uyguladığınızda gerçekleşir:

julia> 1:10 |> sum |> sqrt
7.416198487095663

Burada, sum tarafından üretilen toplam sqrt fonksiyonuna geçirilir. Eşdeğer bileşim şöyle olur:

julia> (sqrt ∘ sum)(1:10)
7.416198487095663

Borulma operatörü, aşağıda açıklanan zincirleme/borulama ve nokta vektörleştirme sözdiziminin faydalı bir kombinasyonunu sağlamak için yayınlama ile de kullanılabilir, yani .|>.

julia> ["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length]
4-element Vector{Any}:
  "A"
  "tsil"
  "Of"
 7

Anonim fonksiyonlarla boruları birleştirirken, sonraki boruların anonim fonksiyonun gövdesinin bir parçası olarak yorumlanmaması için parantez kullanılmalıdır. Karşılaştır:

julia> 1:3 .|> (x -> x^2) |> sum |> sqrt
3.7416573867739413

julia> 1:3 .|> x -> x^2 |> sum |> sqrt
3-element Vector{Float64}:
 1.0
 2.0
 3.0

Dot Syntax for Vectorizing Functions

Teknik hesaplama dillerinde, bir dizinin her bir elemanına f(x) fonksiyonunu uygulayan "vektörleştirilmiş" fonksiyonların bulunması yaygındır ve bu, f(A) ile yeni bir dizi elde etmek için kullanılır. Bu tür bir sözdizimi veri işleme için kullanışlıdır, ancak diğer dillerde performans için de vektörleştirme sıklıkla gereklidir: döngüler yavaşsa, bir fonksiyonun "vektörleştirilmiş" versiyonu, düşük seviyeli bir dilde yazılmış hızlı kütüphane kodunu çağırabilir. Julia'da, vektörleştirilmiş fonksiyonlar performans için zorunlu değildir ve aslında kendi döngülerinizi yazmak genellikle faydalıdır (bkz. Performance Tips), ancak yine de kullanışlı olabilirler. Bu nedenle, herhangi bir Julia fonksiyonu f, f.(A) sözdizimi ile herhangi bir diziye (veya diğer koleksiyonlara) eleman bazında uygulanabilir. Örneğin, sin fonksiyonu, A vektöründeki tüm elemanlara şu şekilde uygulanabilir:

julia> A = [1.0, 2.0, 3.0]
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> sin.(A)
3-element Vector{Float64}:
 0.8414709848078965
 0.9092974268256817
 0.1411200080598672

Elbette, f'nin özel bir "vektör" yöntemini yazarsanız noktayı atlayabilirsiniz, örneğin f(A::AbstractArray) = map(f, A) şeklinde ve bu, f.(A) kadar verimlidir. f.(A) sözdiziminin avantajı, hangi fonksiyonların vektörleştirilebilir olduğunun kütüphane yazarı tarafından önceden belirlenmesine gerek olmamasıdır.

Daha genel olarak, f.(args...) aslında broadcast(f, args...) ile eşdeğerdir; bu, birden fazla dizi (hatta farklı şekillerde) veya diziler ve skalarların karışımı üzerinde işlem yapmanıza olanak tanır (bkz. Broadcasting). Örneğin, f(x, y) = 3x + 4y ifadesine sahipseniz, f.(pi, A) ifadesi, A içindeki her a için f(pi,a) değerlerinden oluşan yeni bir dizi döndürecektir ve f.(vector1, vector2) ifadesi, her i indeksi için f(vector1[i], vector2[i]) değerlerinden oluşan yeni bir vektör döndürecektir (vektörler farklı uzunlukta ise bir istisna fırlatır).

julia> f(x, y) = 3x + 4y;

julia> A = [1.0, 2.0, 3.0];

julia> B = [4.0, 5.0, 6.0];

julia> f.(pi, A)
3-element Vector{Float64}:
 13.42477796076938
 17.42477796076938
 21.42477796076938

julia> f.(A, B)
3-element Vector{Float64}:
 19.0
 26.0
 33.0

Anahtar argümanlar yayılmıyor, ancak her fonksiyon çağrısına basitçe iletiliyor. Örneğin, round.(x, digits=3) ifadesi broadcast(x -> round(x, digits=3), x) ile eşdeğerdir.

Ayrıca, iç içe f.(args...) çağrıları tek bir broadcast döngüsünde birleştirilir. Örneğin, sin.(cos.(X)) ifadesi broadcast(x -> sin(cos(x)), X) ile eşdeğerdir, bu da [sin(cos(x)) for x in X] ile benzerdir: X üzerinde yalnızca tek bir döngü vardır ve sonuç için tek bir dizi tahsis edilir. [Buna karşılık, tipik bir "vektörleştirilmiş" dilde sin(cos(X)) ifadesi önce tmp=cos(X) için bir geçici dizi tahsis eder ve ardından sin(tmp)'yi ayrı bir döngüde hesaplayarak ikinci bir dizi tahsis eder.] Bu döngü birleştirmesi, gerçekleşip gerçekleşmeyeceği bir derleyici optimizasyonu değildir, iç içe f.(args...) çağrılarıyla karşılaşıldığında her zaman bir sentaktik garanti vardır. Teknik olarak, bir "nokta olmayan" fonksiyon çağrısı ile karşılaşıldığında birleştirme durur; örneğin, sin.(sort(cos.(X))) ifadesinde sin ve cos döngüleri, araya giren sort fonksiyonu nedeniyle birleştirilemez.

Sonunda, maksimum verim genellikle bir vektörleştirilmiş işlemin çıktı dizisi önceden ayrıldığında elde edilir, böylece tekrar eden çağrılar sonuçlar için yeni diziler ayırmaz (bkz. Pre-allocating outputs). Bunun için kullanışlı bir sözdizimi X .= ... şeklindedir; bu, broadcast!(identity, X, ...) ile eşdeğerdir, ancak yukarıda olduğu gibi, broadcast! döngüsü herhangi bir iç içe "nokta" çağrılarıyla birleştirilmiştir. Örneğin, X .= sin.(Y) ifadesi broadcast!(sin, X, Y) ile eşdeğerdir ve X'i sin.(Y) ile yerinde günceller. Eğer sol taraf bir dizi indeksleme ifadesiyse, örneğin X[begin+1:end] .= sin.(Y), bu view üzerinde broadcast! olarak çevrilir; örneğin, broadcast!(sin, view(X, firstindex(X)+1:lastindex(X)), Y) şeklindedir, böylece sol taraf yerinde güncellenir.

Nokta ekleme işlemleri ve bir ifadede fonksiyon çağrıları, kodun okunmasını zorlaştıracak şekilde zahmetli olabileceğinden, her bir fonksiyon çağrısını, işlemi ve atamayı "noktalı" versiyonuna dönüştürmek için @. makrosu sağlanmıştır.

julia> Y = [1.0, 2.0, 3.0, 4.0];

julia> X = similar(Y); # pre-allocate output array

julia> @. X = sin(cos(Y)) # equivalent to X .= sin.(cos.(Y))
4-element Vector{Float64}:
  0.5143952585235492
 -0.4042391538522658
 -0.8360218615377305
 -0.6080830096407656

İkili (veya tekil) operatörler, .+ gibi, aynı mekanizma ile işlenir: bunlar broadcast çağrıları ile eşdeğerdir ve diğer iç içe "nokta" çağrıları ile birleştirilir. X .+= Y vb. ifadeleri, X .= X .+ Y ile eşdeğerdir ve birleştirilmiş yerinde atama ile sonuçlanır; ayrıca bkz. dot operators.

Ayrıca, bu örnekte olduğu gibi |> kullanarak nokta işlemlerini fonksiyon zincirleme ile birleştirebilirsiniz:

julia> 1:5 .|> [x->x^2, inv, x->2*x, -, isodd]
5-element Vector{Real}:
    1
    0.5
    6
   -4
 true

Tüm birleşik yayılmadaki fonksiyonlar, sonucun her bir elemanı için her zaman çağrılır. Bu nedenle X .+ σ .* randn.() ifadesi, dizi X'nin her bir elemanına bağımsız ve aynı şekilde örneklenmiş rastgele değerler ekleyecektir, ancak X .+ σ .* randn() ifadesi her bir elemana aynı rastgele örneği ekleyecektir. Birleşik hesaplamanın yayılma yinelemesinin bir veya daha fazla eksen boyunca sabit olduğu durumlarda, ara değerleri tahsis ederek hesaplama sayısını azaltmak için bir alan-zaman takası kullanmak mümkün olabilir. Daha fazla bilgi için performance tips adresine bakın.

Further Reading

Burada belirtmemiz gerekir ki, bu, fonksiyonları tanımlamanın tamamlayıcı bir resmi değildir. Julia, karmaşık bir tür sistemine sahiptir ve argüman türleri üzerinde çoklu dağıtım (multiple dispatch) yapılmasına olanak tanır. Burada verilen örneklerin hiçbiri argümanları üzerinde herhangi bir tür açıklaması sağlamamaktadır, bu da onların tüm argüman türlerine uygulanabilir olduğu anlamına gelir. Tür sistemi Types içinde tanımlanmıştır ve bir fonksiyonu çalışma zamanı argüman türlerine göre seçilen yöntemler ile tanımlamak Methods içinde açıklanmaktadır.