Ders 07 / 21
Fonksiyonlar
Argüman etiketlerini, varsayılan değerleri, demetle çoklu dönüşü ve `inout` parametreyi tanıtır; varsayılan argüman ifadesinin her çağrıda değerlendirildiğini ölçer.
Yayın Güncelleme
Bu derste
- Argüman etiketi, varsayılan değer, demetle dönüş ve `inout` kullanmak
- Varsayılan argüman ifadesinin her çağrıda değerlendirildiğini göstermek
- Argüman verilince varsayılan ifadenin hiç değerlendirilmediğini açıklamak
- Argümanın gövde okumasa da çağrıdan önce değerlendirildiğini söylemek
İçindekiler
Denetim akışında koşullar yalnız gerektiğinde değerlendirildi. Fonksiyonlarda soru bir adım ileri gidiyor: bir çağrının argümanı ne zaman değerlendirilir, bir parametrenin varsayılan değeri ne zaman? Swift’te bir fonksiyon, çağıranın okuyabileceği bir cümle gibi görünmek için tasarlanmıştır; bu dersin ölçümü o cümlenin arkasındaki değerlendirme sırasını gösterir.
Etiketler, Varsayılanlar ve Dönüş
Swift’te her parametrenin bir argüman etiketi (argument label) vardır ve çağrıda yazılır. func move(from start: Int, to end: Int) tanımında from ve to çağıranın gördüğü etiketlerdir, start ve end ise gövdenin kullandığı adlardır. Etiket yerine alt çizgi yazarsan çağrıda etiket olmaz: func pad(_ text: String). Bir parametreye = değer eklemek varsayılan argüman (default argument) tanımlar; çağıran o parametreyi vermezse varsayılan ifade kullanılır.
Bir fonksiyon birden çok değer döndürmek için bir demet (tuple) döndürebilir: -> (low: Int, high: Int). Etiketli demet çağıran tarafta result.low biçiminde okunur. inout parametre ise fonksiyonun çağıranın değişkenini değiştirmesini sağlar; çağrıda değişkenin önüne & yazılır ve değişken var olmak zorundadır.
// Probe.swift — bir ifadenin ne zaman degerlendirildigini kaydeder
final class Probe {
let label: String
private(set) var evaluations = 0
private(set) var uses = 0
private var evaluatedBeforeUse = false
init(_ label: String) {
self.label = label
}
func eval<T>(_ value: T) -> T {
evaluations += 1
if uses == 0 { evaluatedBeforeUse = true }
return value
}
func use<T>(_ access: () -> T) -> T {
uses += 1
return access()
}
var moment: String {
if evaluations == 0 { return "hic" }
if evaluations > 1 { return "her-kullanimda" }
return evaluatedBeforeUse ? "hemen" : "ilk-kullanimda"
}
func report() {
print("\(label): \(evaluations) degerlendirme, \(uses) kullanim -> \(moment)")
}
}
func tally(_ probes: [Probe]) {
for moment in ["hemen", "ilk-kullanimda", "her-kullanimda", "hic"] {
let count = probes.filter { $0.moment == moment }.count
print("\(moment): \(count)")
}
}
Ölçüm bloğu önce bu dört özelliği çalıştırır. Ardından aynı gövdeye sahip iki fonksiyon kurar: pad genişliği çağıran vermezse bir Probe üzerinden varsayılan değeri alır, padGiven ise aynı varsayılana sahiptir ama biz her çağrıda genişliği kendimiz veririz. Fonksiyonlar @MainActor işaretlidir, çünkü varsayılan ifade üst düzeydeki Probe’u okur ve Probe Swift 6 kurallarında yalnız ana aktörden okunabilir.
// main.swift — etiket, varsayilan, demet ve inout
func move(from start: Int, to end: Int) -> Int { end - start }
func bounds(of values: [Int]) -> (low: Int, high: Int) {
(values.min() ?? 0, values.max() ?? 0)
}
func increment(_ value: inout Int) { value += 1 }
print(move(from: 2, to: 5))
let found = bounds(of: [3, 1, 2])
print(found.low, found.high)
var total = 0
increment(&total)
increment(&total)
print(total)
let defaultProbe = Probe("varsayilan-ifade")
let givenProbe = Probe("verilen-iken-varsayilan")
@MainActor func pad(_ text: String, width: Int = defaultProbe.eval(6)) -> String {
String(repeating: "*", count: width) + text
}
@MainActor func padGiven(_ text: String, width: Int = givenProbe.eval(6)) -> String {
String(repeating: "*", count: width) + text
}
print(defaultProbe.use { pad("a") })
print(defaultProbe.use { pad("b") })
print(givenProbe.use { padGiven("c", width: 2) })
print(givenProbe.use { padGiven("d", width: 2) })
let allProbes = [defaultProbe, givenProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
3 1 3 2 ******a ******b **c **d varsayilan-ifade: 2 degerlendirme, 2 kullanim -> her-kullanimda verilen-iken-varsayilan: 0 degerlendirme, 2 kullanim -> hic hemen: 0 ilk-kullanimda: 0 her-kullanimda: 1 hic: 1
move çağrısı 3 verdi, bounds demeti 1 3 olarak okundu ve iki increment çağrısı total değişkenini 2 yaptı. Ölçümün asıl satırları sondadır. varsayilan-ifade, iki çağrıda iki kez değerlendirildi: varsayılan ifade fonksiyon tanımında bir kez hesaplanıp saklanmaz, her çağrıda yeniden çalışır ve sonucu her-kullanimda olur. verilen-iken-varsayilan ise iki çağrıda sıfır değerlendirme gösterdi, çünkü çağıran genişliği kendisi verdi ve varsayılan ifadeye hiç uğranmadı: hic.
DT46. Varsayılan argüman ifadesi @MainActor bir fonksiyonda ve yalnız sabit bir tam sayı için ölçüldü; başka tiplerde ya da nonisolated bağlamda aynı sonucun çıkması bu dersin iddiası değildir.
DT47. Varsayılan ifadenin çağıranın bağlamında mı çağrılanın bağlamında mı çalıştığı bu derste ayırt edilmiyor; ölçülen şey kaç kez ve ne zaman değerlendirildiğidir.
Değişken Sayıda Parametre
Bir parametrenin tipinden sonra üç nokta yazarsan fonksiyon o noktada istediğin kadar argüman alır: func sumAll(of numbers: Int...) -> Int. Fonksiyon gövdesinde numbers sıradan bir [Int] dizisidir. Çağrıda argümanları virgülle yazarsın; hiç argüman vermeye de izin verilir ve bu durumda dizi boş gelir. Bir fonksiyonda birden çok değişken sayıda parametre olabilir; ilkinden sonra gelen parametrenin argüman etiketi taşıması gerekir.
Argümanlar çağrıdan önce değerlendirilir; üç argüman üç ifade demektir. Aşağıdaki blok her argümanını bir Probe’dan geçiren iki çağrı yapar ve bir de argümansız çağrı ekler.
// Variadic.swift — degisken sayida parametre
func sumAll(of numbers: Int...) -> Int {
numbers.reduce(0, +)
}
let variadicProbe = Probe("degisken-sayi")
print(variadicProbe.use {
sumAll(of: variadicProbe.eval(1), variadicProbe.eval(2), variadicProbe.eval(3))
})
print(variadicProbe.use {
sumAll(of: variadicProbe.eval(4), variadicProbe.eval(5))
})
print(sumAll())
variadicProbe.report()
6 9 0 degisken-sayi: 5 degerlendirme, 2 kullanim -> her-kullanimda
Üç sayının toplamı 6, iki sayının toplamı 9, argümansız çağrı 0 verdi. Probe beş değerlendirme gösterdi: ilk çağrıda üç, ikincisinde iki. Her argüman kendi ifadesidir ve çağrıdan önce hesaplanır; fonksiyon gövdesi dizi olarak hazır alır. Bu yüzden an her-kullanimda okunur.
Değişken sayıda parametre çağıran tarafı sadeleştirir, çünkü bir dizi kurmak gerekmez. Argümanların sayısı çağrıya göre değişir; ölçüm bu değişimin değerlendirme sayısını da değiştirdiğini gösteriyor.
DT48. Değişken sayıda parametre Int ile ölçüldü; Swift’te bir diziyi değişken parametreye yaymanın yolu yoktur; dizi almak gerekiyorsa parametre [Int] olarak tanımlanır.
İç İçe Fonksiyon Çevresini Yakalar
Bir fonksiyonun içinde başka bir fonksiyon tanımlayabilirsin. İç fonksiyon, dış fonksiyonun yerel değişkenlerini ve parametrelerini görür ve onları yakalar. Dış fonksiyon iç fonksiyonu döndürürse, yakalanan değişkenler dış fonksiyon bittikten sonra da yaşamaya devam eder. Bu, her çağrıda kendi durumunu taşıyan bir sayaç yapmanın yoludur.
Aşağıdaki blok makeCounter adlı bir fonksiyon yazar. Fonksiyon bir başlangıç değeri alır, içindeki advance adlı iç fonksiyon her çağrıda sayıyı bir artırıp bir Probe’dan geçirir ve iç fonksiyonu döndürür. İki bağımsız sayaç kurulur: ilki iki kez, ikincisi bir kez çağrılır.
// Counter.swift — ic fonksiyon durum tasir
func makeCounter(start: Int, probe: Probe) -> () -> Int {
var current = start
func advance() -> Int {
current += 1
return probe.eval(current)
}
return advance
}
let nestedProbe = Probe("ic-fonksiyon")
let first = makeCounter(start: 10, probe: nestedProbe)
let second = makeCounter(start: 100, probe: nestedProbe)
print(nestedProbe.use { first() })
print(nestedProbe.use { first() })
print(nestedProbe.use { second() })
nestedProbe.report()
11 12 101 ic-fonksiyon: 3 degerlendirme, 3 kullanim -> her-kullanimda
İlk sayaç 11 ve 12, ikinci sayaç 101 verdi. İki sayaç aynı fonksiyondan çıktığı hâlde ayrı bir current yakaladı; biri ilerlerken öbürü etkilenmedi. Üç çağrı üç değerlendirme getirdi ve an her-kullanimda olur. makeCounter bittikten sonra bile current yaşıyor, çünkü iç fonksiyon onu tutuyor.
Bu, bir sonraki dersin konusunu hazırlar: iç fonksiyon, kapanışın adı konmuş hâlidir. Yakalama kuralları aynıdır.
DT49. İç fonksiyon bir Int durumu yakaladı; yakalanan değerin sınıf örneği olduğu durumlar bu dersin dışındadır.
inout Geri Yazar
inout parametre, çağıranın değişkenini fonksiyonun değiştirmesine izin verir. Mekanizma “kopyala, değiştir, geri yaz” biçimindedir: fonksiyon değişkenin bir kopyasını alır, gövdede onu istediği kadar değiştirir ve fonksiyon bitince sonucu değişkene tek seferde geri yazar. Değişkenin gözlemcisi varsa (didSet), gözlemci gövdedeki değişikliklerin her birinde değil, geri yazma anında bir kez çalışır. Doğrudan atama ise her satırda gözlemciyi çalıştırır.
Aşağıdaki blok aynı işi iki yolla yapar: biri inout parametreli bir fonksiyonla iki kez artırır, öbürü iki artırmayı doğrudan yazar. İki değişkenin de bir didSet gözlemcisi var ve her gözlemci kendi Probe’una yazar. İki yol da iki tur çalıştırılır.
// Inout.swift — inout geri yazma
func addTwice(_ value: inout Int) {
value += 1
value += 1
}
let viaCallProbe = Probe("inout-gozlemci")
let directProbe = Probe("dogrudan-gozlemci")
var viaCall = 0 { didSet { _ = viaCallProbe.eval(viaCall) } }
var direct = 0 { didSet { _ = directProbe.eval(direct) } }
_ = viaCallProbe.use { addTwice(&viaCall) }
_ = viaCallProbe.use { addTwice(&viaCall) }
_ = directProbe.use { direct += 1; direct += 1 }
_ = directProbe.use { direct += 1; direct += 1 }
print(viaCall, direct)
viaCallProbe.report()
directProbe.report()
4 4 inout-gozlemci: 2 degerlendirme, 2 kullanim -> her-kullanimda dogrudan-gozlemci: 4 degerlendirme, 2 kullanim -> her-kullanimda
İki değişken de 4 oldu, ama gözlemci sayıları ayrıştı: inout yolu iki değerlendirme, doğrudan yol dört değerlendirme gösterdi. addTwice gövdede iki kez artırdı; gözlemci geri yazma anında, çağrı başına bir kez çalıştı. Doğrudan yolda her += kendi gözlemcisini tetikledi. İki satır da her-kullanimda okunur, ama sayıları fark eder.
Bu fark inout ile gözlemci bir arada olduğunda dikkat gerektirir: gözlemcinin görmediği ara değerler olabilir. Ara durumları görmek istiyorsan doğrudan yaz, yalnız sonucu istiyorsan inout yeterlidir.
DT50. Gözlemci sayısı üst düzey bir değişkenle ölçüldü; sınıf özelliklerinde ve yapı özelliklerinde inout geri yazmasının aynı sayıyı verdiği bu dersin dışındadır.
Fonksiyon Tipi ve Değer Olarak Fonksiyon
Her fonksiyonun bir tipi vardır: parametre tiplerinin ve dönüş tipinin birleşimi. func triple(_ n: Int) -> Int fonksiyonunun tipi (Int) -> Int yazılır. Bu tip, sıradan bir tip gibi kullanılır: bir sabite atayabilir, bir parametre tipi olarak yazabilir ya da başka bir fonksiyondan döndürebilirsin. Fonksiyonu bir sabite atamak onu çağırmaz; yalnız onu bir değer olarak taşır. Çağrı, ancak parantezle yapılır.
Aşağıdaki blok triple fonksiyonunu bir sabite atar, tipini yazdırır ve onu apply adlı başka bir fonksiyona argüman olarak geçirir. apply verilen fonksiyonu bir değere uygular. Uygulanacak değer bir Probe’dan gelir ve apply iki kez çağrılır.
// FunctionType.swift — fonksiyon tipi
func triple(_ n: Int) -> Int { n * 3 }
func apply(_ transform: (Int) -> Int, to value: Int) -> Int {
transform(value)
}
let typeProbe = Probe("fonksiyon-tipi")
let transform = triple
print(type(of: transform))
print(typeProbe.use { apply(transform, to: typeProbe.eval(4)) })
print(typeProbe.use { apply(transform, to: typeProbe.eval(5)) })
typeProbe.report()
@Sendable (Int) -> Int 12 15 fonksiyon-tipi: 2 degerlendirme, 2 kullanim -> her-kullanimda
type(of: transform) @Sendable (Int) -> Int yazdırdı: tip, parametre ve dönüş tipine ek olarak bir @Sendable niteliği taşıyor, çünkü üst düzey fonksiyonlar eşzamanlı ortamlarda güvenle paylaşılabilir. Okunuşta (Int) -> Int kısmı belirleyicidir. apply iki çağrıda 12 ve 15 verdi: 4 * 3 ve 5 * 3. Probe iki değerlendirme gösterdi; her çağrıda argüman, apply çalışmadan önce bir kez hesaplandı. transform sabitinin kendisi bir değerlendirme sayılmadı, çünkü fonksiyonu bir sabite atamak onu çalıştırmaz.
Fonksiyonu değer olarak taşıdığında çağırma anı sende kalır: onu ne zaman, kaç kez ve hangi argümanla çağıracağın, çağrı yerinde belli olur.
Fonksiyon tipini bir kez okumayı öğrendiğinde, standart kütüphanedeki imzalar da okunur hâle gelir. Bir dizinin map yöntemi bir (Element) -> T fonksiyonu bekler; ona adlı bir fonksiyon da verebilirsin, bir kapanış da. Sonraki derste göreceğin kapanış, adı olmayan bir fonksiyon değeridir ve aynı tip kurallarına uyar.
Tipin başındaki @Sendable niteliği şimdilik dikkate alınmayabilir; onu sonraki konularda eşzamanlılıkla birlikte göreceksin. Önemli olan, fonksiyonun bir değer olarak taşındığını ve çağrı parantezi gelmedikçe çalışmadığını bilmektir. Aynı fonksiyonu iki farklı değişkene atamak iki ayrı kopya üretmez; ikisi de aynı fonksiyona işaret eder.
Çağrıdan önce hesaplanan şey argümandır, fonksiyon değeri değil; ölçümün iki değerlendirmesi bunu doğruluyor.
DT51. Fonksiyon tipi tek parametreli bir fonksiyonla ölçüldü; birden çok parametreli ve fonksiyon döndüren fonksiyonlar bu dersin dışındadır.
Sınırlayıcı Ölçüm
Varsayılan değer çağıran vermediğinde devreye girer. Çağıran bir argüman verdiğinde ise iş tersine döner: o ifade çağrıdan önce değerlendirilir, fonksiyon gövdesi onu okusun ya da okumasın. Bu, dil açısından sıradan bir kuraldır, ama ölçüm bunu göstermeden önce bir sezgi bırakır: okunmayan bir parametrenin argümanı hiç değerlendirilmez gibi görünür. Aşağıdaki gövde parametresini hiç okumaz, ve marked yardımcı fonksiyonu argümanın ne zaman çalıştığını print ile gösterir.
// main.swift — argüman gövdeden once degerlendirilir
func marked(_ n: Int) -> Int {
print("arguman \(n)")
return n
}
func ignore(_ value: Int) -> String {
print("govde calisti")
return "okumadi"
}
let argumentProbe = Probe("arguman-ifadesi")
_ = argumentProbe.use { ignore(argumentProbe.eval(marked(7))) }
_ = argumentProbe.use { ignore(argumentProbe.eval(marked(8))) }
let allProbes = [argumentProbe]
for probe in allProbes { probe.report() }
tally(allProbes)
arguman 7 govde calisti arguman 8 govde calisti arguman-ifadesi: 2 degerlendirme, 2 kullanim -> her-kullanimda hemen: 0 ilk-kullanimda: 0 her-kullanimda: 1 hic: 0
arguman 7 satırı govde calisti satırından önce basıldı, hem de gövde value parametresini hiç kullanmadığı hâlde. arguman-ifadesi iki çağrıda iki kez değerlendirildi: argüman, fonksiyon çağrılmadan önce her seferinde hesaplanır. Varsayılan ifade ile arasındaki fark şudur: varsayılan ifade yalnız argüman verilmediğinde çalışır, verilen argüman ise her zaman çalışır.
DT52. ignore gövdesinin parametreyi okumaması, derleyicinin argüman ifadesini eleyip elemediğini ölçmez; çıktıda arguman 7 satırı bu ölçümün tek kanıtıdır ve yalnız bu yapı için geçerlidir.
Özet
- Argüman etiketi çağrıyı okunur kılar;
_etiketi kaldırır,= değervarsayılan argüman tanımlar. - Demet birden çok değer döndürür;
inoutparametre çağıranınvardeğişkenini&ile değiştirir. - Varsayılan argüman ifadesi her çağrıda, argüman verilmediğinde değerlendirilir.
- Argüman verilirse varsayılan ifade hiç değerlendirilmez; verilen argüman gövde okumasa da çağrıdan önce değerlendirilir.
- Bu dersin anı: her kullanımda (varsayılan argüman ifadesi).
Sonraki Adım
Bir fonksiyon, parametresi olarak başka bir fonksiyon alabilir ve bunu isimsiz bir ifade olarak yazabilirsin. Bu ifadenin gövdesi ne zaman çalışır ve çevresindeki değişkenleri hangi anda görür? Sıradaki ders kapanışlara bakıyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.