TypeScript, derleyici uygulamak için de şaşırtıcı derecede iyi
(matklad.github.io)- Derleyici türü araçlar için uygulama dili geleneksel olarak OCaml ve C++ arasında bölünmüş olsa da, küçük dil deneylerinde TypeScript, ML ailesi diller gibi hafifçe kullanılabilecek bir seçenek hâline geliyor
- Rust, ML ve C++’ın güçlü yanlarını birleştirip güvenli çok iş parçacıklılığı da sunsa da, verinin fiziksel yerleşimini modellemek gerektiği için küçük prototipler için yükü artabiliyor
- Deno; tekil binary, yerleşik linting ve formatting, derleme adımının olmaması, task runner ve watch modu sayesinde TypeScript dil deneylerine hızlı başlamayı sağlıyor
- Örnek typechecker; generic AST, tagged union, visitor ve bottom-up transform’u birleştirerek
Expr<void>’uExpr<Type>’a dönüştürüyor,TypeError’ı bir tip değeri olarak koyarak zincirleme hataları azaltıyor - TypeScript; otomatik tamamlama, esnek tip sistemi ve gerektiğinde dinamik tarafa kaçabilen runtime özellikleri sayesinde küçük dil hack’leri için üretken bir araç olabilir
Uygulama dili seçiminin mevcut dengesi
- Derleyici biçiminde araçlar geliştirirken uygulama dili seçiminde kabaca iki akım var
- Biçimsel spesifikasyonlar veya hobi amaçlı oyuncak diller gibi dil odaklı işler için OCaml iyi uyuyor
- Örnek olarak plzoo ve WebAssembly reference interpreter var
- Uygulama odaklı ve production’a hazır olması gereken işlerde C++ sıkça seçiliyor
- LLVM, clang, v8 ve HotSpot’un tamamı C++ tabanlı
- Rust, ML ve C++’tan doğrudan etkilenmiş; iki dilin avantajlarını birleştirirken güvenli çok iş parçacıklılık gibi kendi güçlü yanlarını da sunuyor
- Ancak spektrumda daha çok production’a hazır olma tarafına eğiliyor
- “Öylece çalışan” build sistemi prototiplemeye de yardımcı olsa da, verinin fiziksel yerleşimini modelleme gerektiren ek karmaşıklık getiriyor
Rust’ta indeks yaklaşımının avantajları ve küçük kodlarda yükü
- Rust ile derleyici yazarken sık verilen tavsiye, pointer’lardan kaçınmak ve indeks kullanmak
- İndeksler büyük codebase’lerde çeşitli avantajlara sahip
- Side table’lar ilgili modülün içinde tutulabildiği için coupling azalır
- İndeks
u32olur ve struct-of-arrays yerleşimini teşvik ederek performans açısından avantaj sağlar - Serileştirmek veya incremental compilation framework’lerine bağlamak daha kolaydır; bu da hesaplama stratejisini esnekleştirir
- Ancak küçük ölçekli programlamada indeks yaklaşımının kendisi zahmetli hâle gelir; hobi deneylerinde bu yük kritik olabilir
- OCaml’de eski bir his hâlâ var ve bu bağlamda TypeScript’in ML’e karşılık gelen bir alternatif gibi kullanılıp kullanılamayacağı değerlendiriliyor
Deno ve TypeScript’in sunduğu hızlı deney ortamı
- Başlangıç ortamı olarak deno kullanılıyor
- TypeScript’i doğrudan kullanmaya imkân veren hemen kullanılabilir bir deneyim sunuyor
- OCaml’de bu nokta sancılı; Rust, OCaml veya C++’tan daha iyi olsa da Deno, Rust’tan daha basit bir deneyim veriyor
- Deno’nun geliştirme deneyimi küçük PLT hack’lerine iyi uyuyor
- Tekil binary’dir
- Linting ve formatting yerleşiktir
- Ayrı bir derleme adımı yoktur
- Yerleşik task runner ve watch modu vardır
- TypeScript’in kendisi, yeterince esnek ve sözdizimsel yükü hafif bir tip sistemi sunar
Küçük bir typechecker örneğinde TypeScript kalıpları
- AST, dosya konumu bilgisine sahip ifadelerle başlar
Location,file,line,columnalanlarına sahiptir- TypeScript’te string yalnızca
string, sayı da yalnızcanumberolduğundanusizeileu32gibi ayrımları düşünmeye gerek kalmaz
- İfadelerde konum ve kind ayrılır; daha sonra
Expr<T>biçiminde ilişkili veri generic hâle getirilir- Parsing’in hemen ardından ifadeler
voidverisine sahiptir - Typechecker’ın işlediği ifadeler
Typeverisine sahiptir - Tip çıkarımı fonksiyonu
ast.Expr<void>alıpast.Expr<Type>döndürür
- Parsing’in hemen ardından ifadeler
- TypeScript runtime davranışını otomatik olarak eklemediği için union type’ları eşlemek adına runtime tip bilgisini doğrudan koymak gerekir
tag: "binary",tag: "if"gibi alanlar bu rolü üstlenirtag: "binary", runtime’da ilgili değerin yalnızca"binary"string’i olabileceği anlamına gelir
- Boolean literal ve int literal neredeyse aynı biçimde olduğundan
ExprLiteral<T, V, Tag>ile soyutlanırExprBool<T>,ExprLiteral<T, boolean, "bool">olurExprInt<T>,ExprLiteral<T, number, "int">olur
- Tip değerleri
TypeBoolveTypeIntolarak ayrılır; singleton değerler de aynı adla sunulur- TypeScript tipleri tamamen sildiği için tipe ilişkin adlar ile değere ilişkin adlar ayrı namespace’lerde bulunur
- Bu özellikten yararlanarak tip ve değer aynı adla tanımlanabilir
Visitor, transform, hata tipi ve desugaring
- TypeScript’te
switchbir ifade değil statement olduğu için, ifade kind’larını rahat işlemek adına visitor tanımlanırbool,int,binary,ifmetotları ilgili kind’ı işler- Editör otomatik tamamlaması
switchcase’lerinde ve visitor uygulamasında yardımcı olur
transform<U, V>,Expr<U>’yuExpr<V>’ye dönüştüren genelleştirilmiş bir traversal fonksiyonudur- Dönüşüm bottom-up çalışır
- İç node’lar ziyaret edilirken alt ifadeler zaten dönüştürülmüş olduğundan visitor tipi
Visitor<U, V>değilVisitor<V, V>olur
- TypeScript’in dinamik tipli bir dil olmasından yararlanarak
Object.keysile daha genel bir traversal da yapılabilir- Bu durumda bile statik fonksiyon imzası korunabilir
- Örnekte şart değil, ancak gerekirse dinamik yaklaşıma kaçma alanı vardır
- Tip hataları side effect olarak bir array’de biriktirilmez;
TypeErrortipiyle temsil edilirType,TypeBool | TypeInt | TypeErrorolurTypeError,tag: "Error",location,messagealanlarına sahiptirtype_equal, taraflardan biriErrorise true döndürerek zincirleme başarısızlığı engeller
- Nihai typechecker, binary ve if ifadelerini kontrol eder
- Binary ifade, sol ve sağ operand tipleri farklıysa
"binary expression operands have different types"hatasını döndürür - If ifadesi, koşul boolean değilse
"if condition is not a boolean"hatasını döndürür - Then ve else branch tipleri farklıysa
"if branches have different types"hatasını döndürür
- Binary ifade, sol ve sağ operand tipleri farklıysa
- Ortaya çıkan kod bir miktar typing gerektirir; ancak otomatik tamamlama bunu büyük ölçüde telafi eder, dille mücadele ediyormuş hissi azdır ve problemin biçimine doğal şekilde uyar
- TypeScript’in küçük dil hack’leri için üretken olmasının nedenleri üç maddede özetlenir
- Deno küçük, kapalı, güçlü ve etkili geliştirme workflow’una optimize edilmiş bir scripting runtime’ıdır
- TypeScript araçları, IDE’yi kullanışlı ve üretken kılar; Deno sayesinde yapılandırma gerekmez
- Dil hem runtime hem compile time tarafında güçlüdür; tiplerle oldukça incelikli ifadeler kurarken gerektiğinde dinamik yaklaşıma kaçabilir
- Ek bir fikir olarak, çok sayıda syntactic sugar’ı tip güvenli biçimde desugar etmek de mümkündür
ExprveExprKind, ilişkili veri yerine tümExprKindüzerinde recursive olarak parametrik hâle getirilirExprKindCore, temel ifade kümesini temsil ederExprKindSugar, temel ifadeleri veya temel ifadelere desugar edilebilen ifadeleri içerirdesugar(expr: ExprSugar): ExprCore, syntactic sugar ifadelerini çekirdek ifadelere indirgerdesugar_one(expr: ExprKindSugar<ExprCore>): ExprKindCore<ExprCore>, alt ifadeler zaten desugar edilmişken tek adımlık dönüşüm yapar
1 yorum
Hacker News görüşleri
TypeScript genel olarak harika bir dil; fonksiyonların özelliklere/metotlara sahip olabilen nesneler olması ise yeterince takdir edilmiyor.
Bir fonksiyon dizisini komutlar gibi çalıştırıp daha sonra
helpgibi açıklamalar ekleyebilir ya da closure/kısmi uygulama ile durum ekleyebilirsiniz; böyleceCommandgibi sınıfları aceleyle tanımlamanıza gerek kalmaz.Nesne yönelimli programlamada şeylere fazla erken isim vermek pek çok çatışma yaratıyor;
VideoCompressor#compress()gibi bir yapı yerine gerekli değerleri fonksiyona geçirmek daha doğal görünüyor.Fonksiyon gibi davranan nesneleri destekleyen diğer diller https://en.wikipedia.org/wiki/Function_object adresinde derlenmiş.
net/http’deki handler’larda olduğu gibi bir structservemetodunu uygulayabilir ya da bir handler fonksiyonu kendi kendini çağırarak arayüzü karşılayabilir.Clojure’da fonksiyon
var’larına metadata ekleyerek benzer şekilde ele alınabilir; Lisp olduğu için makrolarla neredeyse her şey mümkündür.Ayrıca
core/async’in CSP kanalları çalıştırma ile iletişimi ayırarak callback/promise/async/awaitgibi fonksiyon rengi sorunlarından kaçınmayı sağlar; komutlar da sonuçlarını kanala gönderen üreticiler gibi davranabilir.Metot adı ne olursa olsun fonksiyon bağlamında kullanılabilir ve somut metot adına referans vermek gerekmez.
Ancak bir fonksiyonun özellikler taşıyıp durumu içinde barındırmasını ve aynı argümanlarla çağrılsa bile davranışının değişebilmesini tercih etmiyorum. Fonksiyonel programlamanın büyük avantajının, nesne yönelimli tarzdaki durumdan uzaklaşmak olduğunu düşünüyorum.
Func, dönüş değeri olmayan fonksiyonlarActionile benzer biçimde ifade edilebilir.JavaScript’in lambda ifadeleri C#’a oldukça benzer; TypeScript ve C#’ın fonksiyon imzaları da epey benzerdir.
JavaScript, TypeScript ve C# arasındaki benzerlikleri gösteren küçük bir depo da var: https://github.com/CharlieDigital/js-ts-csharp
Aynı mantığı JS/TS/C# olarak yan yana gösteren ekran görüntüsü: https://github.com/CharlieDigital/js-ts-csharp/blob/main/js-...
Çok şaşırtıcı değil. TypeScript de sonuçta ML ailesi özelliklerinin epey bir kısmını güç bela benimsemiş başka bir dil gibi görünüyor.
Gerçek pattern matching olmadığı için OCaml’dan daha zahmetli, ama C#, Swift, Dart, Kotlin gibi dillerle karşılaştırıldığında fena sayılmaz.
TypeScript’in tip sistemi güçlü, ama temel standart kütüphanesi ve dilin kendisi yetersiz kalıyor; pattern matching/switch expression yok.
Dart’ta nesne modeli kapalı olduğu için dinamik özgürlük daha düşük; tip sistemi ve ifadeleri de daha zayıf, metaprogramlama olanakları neredeyse yok; bu yüzden Java tarzı boilerplate’e ve kod üreteçlerine bel bağlıyor.
C#, adı geçen diller arasında ML özelliklerine en yakın olanı; fakat TypeScript’in aksine sum type’ları olmadığı için pek çok iş daha zahmetli hale geliyor.
Örneğin
{ ... }yapısını tanıyan bir parser’ı başka parser’larla birleştirebilir, bir ifadeyi de kontrol akışı/deklarasyon/atama seçeneklerinden biri olarak tanımlayabilirsiniz.ML tarzı liste işleme ve pattern matching, ara temsilleri ele alırken çok ifade gücü yüksek oluyordu.
Metinde de
switchbir ifade olmadığı için visitor pattern ile dolanmak gerekmişti; JavaScript’in iterator desteği de garip biçimde yetersiz..map()var, ama yalnızca dizilerde çalışıyor; genel iterator’lara doğrudan uygulanamıyor.Farkları çok olsa da geçiş zor değil; kişisel olarak Kotlin’i daha çok tercih etsem de ikisi de kullanılabilir.
TypeScript, JavaScript uyumluluğundan çıkıp WASM’a derlense nasıl olur diye düşünüyorum. Kotlin bir WASM derleyicisi ekliyor ve zaten bir JS transpiler’ı da var; aynı kod WASM’de daha küçük oluyor ve daha hızlı yükleniyor.
Tarayıcı JavaScript’i iyi bir derleme hedefi değil; yeni projelerin baştan TypeScript ile başlaması arttıkça, mevcut JavaScript’ten kolay geçiş yapılması gerektiği gerekçesi de giderek zayıflıyor.
Rust ile TypeScript arasında gidip gelince tagged enum gibi özelliklerin yokluğu çok net hissediliyor.
ADT enum önerisi durmuş gibi görünüyor; başka bir çaba var mı merak ediyorum: https://github.com/Jack-Works/proposal-enum/discussions/19
TypeScript’in tip sistemi eğlenceli, ama derleyici derlenen bir dille yazılmış olsaydı ne kadar daha hızlı olurdu diye insan merak ediyor
Elbette bunun için “iyi bir implementasyon” gibi büyük bir önkoşul var
swcveesbuildiyi karşılaştırma noktaları değil. Hız artışının önemli bir kısmı, TypeScript’e özgü söz dizimini kaldırıp JavaScript üretmelerinden geliyortscyalnızca ilk çalıştırmada yavaş sayılır;incrementalbayrağı ya da--transpile-onlyizleme modu kullanıldığında derleme süresi genelde 100 ms’nin altına iner ve SWC veya ESBuild ile hissedilir fark neredeyse kalmazOrta ölçekli bir programın tip denetimi 20 saniye sürüyorsa bunun çoğu zaman JS yüzünden değil, tiplerin kombinatoryal patlama yaratmasından kaynaklandığını söylüyor
Başka çalışma ortamları paralellik veya başlangıç süresi açısından kazanç sağlayabilir, ama toplamda 20 kat hızlanmayı destekleyen CPU odaklı bir benchmark görmediklerini belirtiyor
Sonra 5 satırlık tip mantığını bütün gün debug edince insan kendine buraya nasıl geldiğini soruyor
SWC’nin ana geliştiricilerinden birinin Rust ile yazdığı bir proje; SWC TS’i JS’e derlerken STC TS tiplerini denetliyor
Yaklaşan isolated declaration mode’un derleme süresini %75’e kadar azaltabileceği söyleniyor: https://github.com/microsoft/TypeScript/pull/53463#issuecomm...
Derleyicileri yeni öğrenmeye başlayanlara şu kitabı öneririm: https://keleshev.com/compiling-to-assembly-from-scratch/
Yazar, TypeScript’in bir alt kümesiyle 32 bit ARM assembly derleyicisi yapıyor; neredeyse sözde kod gibi göründüğü için erişilebilir olduğunu açıklıyor
Kitap iki bölüme ayrılıyor; ilkinde Java ile bir dil yorumlayıcısı yapılıyor, ikincisinde aynı dil bytecode’a derlenip C ile bir bytecode sanal makinesi implemente ediliyor
Implementasyondaki her kod satırına kitapta referans veriliyor
Visitor pattern’dan kaçınmak için
runyardımcı fonksiyonuyla IIFE tarzı switch kullanılabilirHemen çağrılan bir fonksiyonun içinde
switchkullanıp dönüş tipinin çıkarılmasını sağlamak yeterliSondaki
()yüzünden IIFE’den kaçınmak istiyorsanız fonksiyonu ayrı tanımlayıp sonra çağırabilirsinizTypeScript ile bir derleyici yazıyorum ve sanıldığı kadar kötü olmadığına katılıyorum
Başta yazar gibi Deno ile başladım ama sonunda Bun’a geçtim; pürüzlü yerleri olsa da Deno’dan daha memnunum ve çok hızlı
Standart parser generator frontend’i olarak Ohm-js oldukça rahat: https://ohmjs.org/
Resmî
tscderleyicisi okunamayacak kadar büyük, önermem;tsc’nin nasıl çalıştığını görmek için mini-typescript daha iyi: https://github.com/sandersn/mini-typescript/Özellikle
centi-typescriptbranch’i yardımcı oluyor: https://github.com/sandersn/mini-typescript/tree/centi-types...WASM’de GC ve DOM erişiminin mümkün hâle gelmesini bekliyorum
TypeScript’in interface’ler yüzünden ek yükünün daha fazla olacağını sanıyordum, şaşırtıcı
Başka alanlara da uygulanabilir mi, örneğin dil ayrıştırma için de iyi olur mu merak ediyorum
Derleme sürecinde tamamen ortadan kalkıyorlar
Sonuç çok şaşırtıcı değil. Çünkü TypeScript derleyicisinin kendisi TypeScript ile yazılmış durumda
ML olarak TypeScript zaten her gün ağır production ortamlarında kendini kanıtlıyor
C# ile derleyici yazmıştım; burada özel görünen şey en fazla union type
Kişisel olarak visitor pattern’ın laf kalabalığından kaçınmaya karar verdim ve derleme zamanında tamlık denetimi için kapalı enum özelliğini bekliyorum
Alternatifler genelde garip oluyor. Bir sum type’ı temsil eden sınıfta nullable N adet property tutup “her zaman yalnızca biri non-null” şeklindeki dokümantasyondaki koşula güvenmek ya da birden fazla sınıfı ortak bir sınıftan türetmek; ikisi de ağır hissettiriyor
Örtüşen birden fazla union type yapmak için iki yöntem de tekrar ya da akıllıca kombinasyonlar gerektiriyor