Ders 02 / 12
Çerçeveler ve Kütüphaneler
İki içe alınan modülün görünür adlarının birleştiğinin, aynı adın ret verdiğinin ve yerel tanımın içe alınanı gölgelediğinin ölçümü.
Yayın Güncelleme
Bu derste
- Çerçeve ile kütüphane arasındaki farkı kodu kimin çağırdığı üzerinden ayırır
- İki modülün görünür adlarının birleşim olarak toplandığını ölçer
- Aynı adın iki modülde nitelenmeden kullanılmasının ret verdiğini ölçer
- Modülle nitelenen adın buluşmayı kaldırdığını gösterir
İçindekiler
Önceki derste sekiz buluşmanın dördünü dört çözüme ayırdık; ad arama buluşmalarından biri öncelik, biri ret verdi. Bu ders o iki buluşmayı yakından okur. Bir uygulama birden çok modülü içe aldığında, her modülün açtığı adlar aynı kapsamda buluşur. Soru değişmez: bu iki tanım buluşunca nasıl çözülüyor? Bu derste bakılan çözüm birleşim.
Önce iki kavramı ayıralım. Kütüphane (library), uygulamanın çağırdığı hazır koddur: ne zaman çağıracağını uygulama seçer. Çerçeve (framework) ise kendi iskeletini kurar ve uygulamanın kodunu o iskeletin içinde çağırır; uygulama bir olayı işleyen bir parça olarak çerçevenin akışına girer. Platformda sistemle gelen çerçevelere sistem çerçevesi denir, uygulamanın bir bağımlılık olarak eklediği koda ise çoğunlukla paket ya da kütüphane. Her ikisi de bir modül olarak içe alınır ve ikisinin de açtığı adlar aynı kaynak dosyada yan yana durur. Ölçülen şey de bu yan yana gelmedir.
// Meeting.swift — iki tanim bulusunca nasil cozuldugunu siniflandirir
struct Meeting {
let label: String
let first: Set<String>
let second: Set<String>
let result: Set<String>?
init(_ label: String, first: Set<String>, second: Set<String>, result: Set<String>?) {
self.label = label
self.first = first
self.second = second
self.result = result
}
var resolution: String {
if first.isEmpty || second.isEmpty
|| first.isSubset(of: second) || second.isSubset(of: first) {
return "olcum-disi"
}
guard let result, !result.isEmpty else { return "ret" }
if result == first.union(second) { return "birlesim" }
if result == first.intersection(second) { return "kesisim" }
if result == first || result == second { return "oncelik" }
return "kural-disi"
}
static func list(_ values: Set<String>) -> String {
values.isEmpty ? "bos" : values.sorted().joined(separator: ",")
}
func report() {
let outcome = result.map(Meeting.list) ?? "yok"
print("\(label): ilk \(Meeting.list(first)) | ikinci \(Meeting.list(second)) | sonuc \(outcome) -> \(resolution)")
}
}
func summarize(_ meetings: [Meeting]) {
for resolution in ["oncelik", "kesisim", "birlesim", "ret"] {
let count = meetings.filter { $0.resolution == resolution }.count
print("\(resolution): \(count)")
}
}
Bir modül, dışarıya açtığı adlardan oluşan bir yüzeydir. Aşağıdaki yardımcı üç şeyi modeller: bir modülün adı ve açtığı adlar, iki ya da daha çok modül içe alındığında görünen adların toplamı, ve bir adın hangi tanımdan geldiğini bulan arama. Arama önce yerel tanıma bakar; yerel tanım yoksa içe alınan modüller arasında bulur; bulunan tanım tek değilse sonuç yoktur. Bir de modül adıyla nitelenmiş kullanım vardır: Graphics.Canvas yazıldığında yalnız Graphics modülünün adı aday olur. Bu dosyanın çıktısı yoktur; yardımcı olarak durur ve sonraki ölçülen bloklarla birlikte derlenir.
// Modules.swift — modul yuzeyi, gorunen adlar ve ad arama
struct Module {
let name: String
let exports: [String]
}
func visibleNames(of modules: [Module]) -> Set<String> {
Set(modules.flatMap { $0.exports })
}
func qualified(_ name: String, from module: Module) -> Set<String>? {
module.exports.contains(name) ? [module.name + "." + name] : nil
}
func owners(of name: String, in modules: [Module], local: Set<String>) -> Set<String>? {
if local.contains(name) { return ["yerel." + name] }
let found = modules.filter { $0.exports.contains(name) }.map { $0.name + "." + name }
return found.count == 1 ? Set(found) : nil
}
İki Modülü İçe Almak
İki modül içe alındığında ikisinin açtığı adlar aynı kapsamda görünür. Aşağıdaki ölçüm bir grafik modülü ile bir depolama modülü kurar ve ikisinin açtığı adlara visibleNames ile bakar. Ortak ad yoktur; bu yüzden hiçbir ad ötekinin yerine geçmez ve bir tanım ötekini ezmez.
// main.swift — iki modulu ice almak gorunen adlari toplar
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let storage = Module(name: "Storage", exports: ["load", "save"])
let imports = Meeting("gorunen-adlar",
first: Set(graphics.exports),
second: Set(storage.exports),
result: visibleNames(of: [graphics, storage]))
imports.report()
gorunen-adlar: ilk Canvas,draw,render | ikinci load,save | sonuc Canvas,draw,load,render,save -> birlesim
Sonuç iki yüzeyin tümüdür: beş adın hepsi görünür, hiçbiri düşmedi. Bu birleşimdir ve çerçeve eklemenin olağan sonucudur; yeni bir modül içe alındığında uygulamanın görebildiği ad sayısı azalmaz, artar. Bu aynı zamanda tehlikenin kaynağıdır: görünür ad sayısı büyüdükçe iki modülün aynı adı açma ihtimali de büyür.
PY4. Bu ölçüm iki modüllük ve ortak adı olmayan bir çift için. Bir modülün adlarının ötekinin alt kümesi olduğu durum ölçülmedi; o durumda buluşma olcum-disi olur.
Sistem Çerçevesi ve Paket
İçe alınan modülün nereden geldiği, adların nasıl buluştuğunu değiştirmez ama kimin kontrolünde olduğunu değiştirir. Sistem çerçeveleri aygıtta işletim sistemiyle birlikte bulunur; uygulama onları derleme sırasında bağlar ve çalışırken sistemin kendi kopyasını kullanır. Bu yüzden sürümleri uygulamanın değil, çalıştığı işletim sisteminin sürümüne bağlıdır ve bir adın var olup olmadığı o sürüme göre değişebilir; bu konuyu sürüm dersinde ölçeceğiz. Paket olarak eklenen kütüphane ise bir bağımlılık bildirimiyle istenir, derleme sırasında çözülür ve uygulamayla birlikte dağıtılır; sürümünü uygulama seçer.
Her import satırı tek bir dosya için geçerlidir. Bir dosyada görünen adlar, o dosyanın içe aldığı modüllerin yüzeylerinin birleşimi artı uygulamanın kendi tanımlarıdır. Başka bir dosya, aynı modülü içe almadıkça o modülün üst düzey adlarını göremez. Yani buluşma, bir uygulama için tek seferlik değil, dosya başına ayrı kurulan bir buluşmadır: iki modülün çakıştığı adı yalnız ikisini birden içe alan dosyalar yaşar.
Aynı Ad ve Yerel Tanım
İki modül aynı adı açtığında ve kullanım nitelenmediğinde, arama iki aday bulur. Hangisinin kastedildiğine dair bir bilgi yoktur ve derleyici birini seçmez: sonuç yoktur, çözüm ret. Aynı ad uygulamanın kendi kaynağında da tanımlıysa tablo değişir. Yerel tanım içe alınanın önüne geçer; yani içe alınan ad hâlâ vardır ama aramada görünmez, buna gölgeleme (shadowing) denir ve çözüm öncelik olur. Aşağıdaki ölçüm iki durumu da kurar: grafik ve grafik çizelgeleri modüllerinin ikisi de Canvas adını açar, ve uygulama kendi render adını tanımlar.
// main.swift — ayni ad belirsiz kalir, yerel tanim onu golgeler
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])
let local: Set = ["render"]
let cases = [
Meeting("ayni-ad",
first: qualified("Canvas", from: graphics)!,
second: qualified("Canvas", from: charts)!,
result: owners(of: "Canvas", in: [graphics, charts], local: [])),
Meeting("yerel-tanim",
first: qualified("render", from: graphics)!,
second: owners(of: "render", in: [], local: local)!,
result: owners(of: "render", in: [graphics, charts], local: local)),
]
for item in cases {
item.report()
}
summarize(cases)
ayni-ad: ilk Graphics.Canvas | ikinci Charts.Canvas | sonuc yok -> ret yerel-tanim: ilk Graphics.render | ikinci yerel.render | sonuc yerel.render -> oncelik oncelik: 1 kesisim: 0 birlesim: 0 ret: 1
ayni-ad satırında sonuç yok: arama iki aday gördü ve hiçbirini seçmedi. yerel-tanim satırında sonuç ikinci taraftır, yani yerel tanım; içe alınan Graphics.render düştü. Aynı iki modülün yüzeyleri bir önceki ölçümde birleşim vermişti; buluşmanın çözümü yüzeylerin kendisinden değil, aranan adın kimin elinde olduğundan çıktı. Yüzeylerin toplamı bir ad aranmadığı sürece sorun çıkarmaz; arama başladığında aranan ad tek bir tanıma çözülmek zorundadır.
Aynı gölgelemeyi gerçek bir sistem modülüyle kurmak için ders dışı bir örnek şöyledir. Parça platform modülüne dayandığı için kaynak olarak durur ve çıktısı iddia edilmez.
// Kaynak parca: yerel Data tanimi Foundation.Data adini golgeler
import Foundation
struct Data {
let bytes: [UInt8]
}
let own = Data(bytes: [1, 2])
let system: Foundation.Data = Foundation.Data()
Yerel Data yalın adı alır; sistem çerçevesindeki tip ancak modül adıyla nitelenerek erişilebilir kalır. Model, platformun bu kuralını kurar: ölçülen bloktaki arama, aynı önceliği yerel-tanim satırında yeniden üretir.
PY5. Bu ölçüm yalnız ad aramasının önceliği için ve iki içe alınan modülün aynı adı açtığı çift için. Bir adın hem bir tipte hem bir işlevde tanımlı olduğu durum, aşırı yükleme ile çözülen adlar ve üç modülün aynı adı açtığı durum ölçülmedi.
Modülün Açtığı Yüzey
Bir modülün dışarıya açtığı yüzey (public surface), onu içe alan kodun görebildiği bildirimlerin tümüdür: tipler, işlevler, sabitler, protokoller. Modülün içinde kalan bildirimler yüzeye girmez ve içe alan kod onlara ulaşamaz. Bu ayrım, kütüphane ile çerçeve arasındaki farkın pratikteki yüzüdür. Kütüphane genellikle küçük bir yüzey açar ve çağıran kod ne zaman hangi işlevi çağıracağını kendisi seçer. Çerçeve ise daha geniş bir yüzey açar; bu yüzeyin önemli bir bölümü, uygulamanın doldurması beklenen yerlerden oluşur: bir protokole uymak, bir sınıftan türemek, bir olay işleyicisi yazmak. Akışı çerçeve yönetir, uygulama kendi parçasını verir.
Yüzeyin genişliği ad çakışması ihtimalini de belirler. Bir kaynak dosya birkaç modülü içe aldığında, her modülün açtığı adlar o dosyada görünür olur. Modül sayısı arttıkça ve yüzeyler genişledikçe iki modülün aynı adı açma ihtimali büyür. Derleyici bu çakışmayı içe alma anında bildirmez; ad kullanıldığı yerde çözülemezse bildirir. Bu yüzden önceki bölümlerdeki ayni-ad ölçümünde çözüm, adı kullanan satıra gelene kadar ortaya çıkmaz: iki tanım yan yana durur, ret ancak kullanım anında yaşanır.
Bir diğer sonuç, yüzeyin bir sözleşme olmasıdır. Sistem çerçevesinin yüzeyi sistem sürümüyle birlikte gelir ve uygulama onu değiştiremez. Bir paketin yüzeyi ise uygulamanın bağımlılık olarak seçtiği sürümle sabitlenir ve paketi yazan taraf onu değiştirdiğinde uygulama yeni sürümü seçene kadar etkilenmez. Hangi tarafın yüzeyi hangi hızla değiştirebildiği, hangi bağımlılığın nasıl ele alınacağını belirler; bu ölçümün konusu değil, bu bölümün yalnız bağlamıdır.
Nitelenmiş Ad Ne Zaman Gerekir
Nitelenmiş ad (qualified name), bir adın başına modülün adını koyarak o adın hangi modülden geldiğini açıkça söylemektir. Çakışma olmadığı sürece niteleme gereksizdir ve okumayı ağırlaştırır; ad yalnız bir modülde varsa zaten tek bir anlamı vardır. Çakışma olduğunda ise niteleme, iki adayın arasından birini seçmenin dildeki yoludur.
Niteleme yerel tanımla farklı çalışır. Yerel tanım, uygulamanın kendi modülünde yazılan bildirimdir ve içe alınan modüllerin açtığı aynı adlı bildirimlerin önüne geçer; çözüm öncelik çıkar ve okuyan kişi bunu görmeden de bu sonuca varabilir. Niteleme ise okuyana bunu görünür kılar: ad, hangi modülden geldiğini kendisi söyler ve çözüm artık buluşmaya bağlı kalmaz, ortada tek bir tanım kalır. Dersin sonundaki sınırlayıcı ölçümde nitelenmis-ad buluşmasının ölçüm dışı çıkmasının nedeni de budur: niteleme iki tanımı buluşturmaz, buluşmayı ortadan kaldırır.
Pratik bir kural şudur: çakışma yoksa nitelemeyin; çakışma varsa ve yerel tanım istenen sonuç değilse nitelemek yerine adı değiştirmeyi düşünün. İçe alınan modülün adını değiştirmek elinizde olmayabilir, ama yerel tanımın adını değiştirmek olur. Niteleme, adı değiştirmenin mümkün olmadığı durumların aracıdır.
Sınırlayıcı Ölçüm
Ret her zaman son durum değildir. Kullanım modül adıyla nitelendiğinde aday küme teke iner, çünkü yalnız yazılan modülün adı aranır. Bu da buluşmayı ortadan kaldırır: nitelemeden önceki adaylar iki, sonraki aday birdir ve ikincisi birincinin alt kümesidir. Aşağıdaki ölçüm bunu kurar.
// main.swift — modulle nitelenen ad adayi teke indirir
let graphics = Module(name: "Graphics", exports: ["Canvas", "draw", "render"])
let charts = Module(name: "Charts", exports: ["Canvas", "plot"])
let all = qualified("Canvas", from: graphics)!.union(qualified("Canvas", from: charts)!)
let written = all.filter { $0.hasPrefix("Graphics.") }
let narrowed = Meeting("nitelenmis-ad", first: all, second: written, result: written)
narrowed.report()
nitelenmis-ad: ilk Charts.Canvas,Graphics.Canvas | ikinci Graphics.Canvas | sonuc Graphics.Canvas -> olcum-disi
Çıktı olcum-disi: ikinci aday kümesi ilkinin alt kümesi, dolayısıyla iki tanım artık buluşmuyor. Ret çözülebilir bir durumdur. Burada çakışan iki ad da içe alınan modüllere aittir ve hiçbirinin adını değiştirmek sizin elinizde değildir; yukarıdaki kuralın niteleme için ayırdığı durum tam olarak budur ve çözüm adı nitelemektir. Birleşim ise ad kullanılmadığı sürece sorun çıkarmaz.
PY6. Bu ölçüm yalnız Module.name önekine göre süzmek için; modül adının kendisinin başka bir modülün adıyla çakıştığı durum ölçülmedi.
Özet
- Çerçeve uygulamanın kodunu kendi akışında çağırır, kütüphaneyi uygulama çağırır; ikisi de modül olarak içe alınır ve açtıkları adlar aynı kapsamda buluşur.
- İki modülün yüzeyi, ortak ad olmadığında birleşim verir: görünür ad sayısı yalnız artar.
- Aynı ad iki modülde nitelenmeden kullanılırsa arama sonuç vermez ve çözüm ret olur.
- Yerel tanım içe alınan adı gölgeler; bu buluşmanın çözümü önceliktir.
- Modülle nitelenen ad aday kümesini teke indirir ve buluşma kalkar: çıktı
olcum-disi. - Bu dersin çözümü: birleşim.
Sonraki Adım
Bu derste iki tanım ad olarak buluştu. Sıradaki derste buluşan şey bir yetki: uygulamanın istediği izinler ile kullanıcının verdikleri. Kullanıcının verisine ya da aygıtın bir yeteneğine dokunan alan çerçeveleri (konum, sağlık, medya, artırılmış gerçeklik) bu yetkileri ister; çözüm kesişimdir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.