İçeriğe atla
Nar v0.6.0
Ctrl K
Dene Bileşenler Kaynak

Nar dil rehberi · v0.6.0

Az bilgiyle çok şey yapabileceğin bir programlama dili

Nar; web, masaüstü, Linux, Android ve iOS için tek kaynaktan uygulama yazmayı hedefleyen bir programlama dilidir. Bu sayfa dilin tamamını açıklamalarıyla ve çalışan örnekleriyle gösterir.

Kurulum yok: deneme alanı derleyiciyi tarayıcında çalıştırır. Yazdığın kodun bağlantısını paylaşabilirsin.

  • web
  • masaüstü
  • Linux
  • Android
  • iOS
nardene
struct Dil {
  ad: String
  hedefler: [String]
}

fn tanit(d: Dil) -> String =
  "${d.ad}: tek kaynaktan ${d.hedefler.len()} hedef"

fn main() {
  let nar = Dil {
    ad: "Nar",
    hedefler: ["web", "masaüstü", "mobil"]
  }
  print(tanit(nar))
  print(nar.hedefler.join(" · "))

  let sayilar = [3, 1, 4, 1, 5, 9, 2, 6]
  print("çiftler:", sayilar.filter(|n| n % 2 == 0))
  print("toplam:", sayilar.toplam())
}
çıktı
Nar: tek kaynaktan 3 hedef
web · masaüstü · mobil
çiftler: [4, 2, 6]
toplam: 31

Nasıl çalıştırılır

Bilgisayarında Python ve Node.js kurulu olması yeterli. Programını bir dosyaya yaz (örneğin deneme.nar) ve çalıştır:

cd C:\Users\mstfa\Desktop\yazılım_dili
.\nar.cmd run deneme.nar

Kodunu çalıştırmadan sadece kontrol ettirmek istersen .\nar.cmd check deneme.nar yeter. Web sayfası üretmek için .\nar.cmd build deneme.nar --target web.

Aşağıdaki örneklerin çoğu fn main() { ... } içine yazılır; kısa tutmak için o sarmalayıcı gösterilmiyor. Üst düzey bildirim (fn, struct, enum) içeren örnekler tam gösterilir.

Başlangıç3 konu

İlk programı yaz, çalıştır, ekrana bir şey yazdır.

İlk program

Her Nar programı main adlı bir fonksiyonla başlar. Program çalıştığında ilk olarak buranın içi yürütülür. print ekrana bir şey yazar.
nardene
fn main() {
  print("Merhaba dünya!")
}
çıktı
Merhaba dünya!

Yorumlar

Yorum, programın çalışmasını etkilemeyen not demektir. Kendine ya da kodu okuyacak kişiye açıklama bırakmak için kullanılır.
nardene
// Bu satır bir yorum, çalıştırılmaz.

/* Birden çok satırı
   kaplayan yorum da yazılabilir. */

print("yorumlar çıktıyı etkilemez")
çıktı
yorumlar çıktıyı etkilemez

Yazdırma

print virgülle ayrılmış birden çok değer alabilir; aralarına boşluk koyar. Her türlü değeri yazdırabilir — sayı, metin, liste, kendi tanımladığın tipler.
nardene
print("tek değer")
print("ad:", "Ayşe", "yaş:", 30)
print([1, 2, 3])
print(3.5, true, none)
çıktı
tek değer
ad: Ayşe yaş: 30
[1, 2, 3]
3.5 true none

Değişkenler ve veri7 konu

Değer tutmak: let ve var, sayılar, metinler, doğru/yanlış.

let ve var

let ile tanımlanan değer bir daha değişmez. Değiştirmen gerekiyorsa var kullan.

Varsayılan olarak let kullan: neyin değiştiğini görmek kodu okumayı kolaylaştırır. Değiştirmeye çalışırsan derleyici uyarır.

nardene
let ad = "Mustafa"      // değişmez
var sayac = 0           // değişebilir

sayac = sayac + 1
sayac += 5              // aynı şeyin kısa yazımı

print(ad, sayac)
çıktı
Mustafa 6

Tipler

Her değerin bir tipi vardır. Genelde yazmana gerek yok — Nar değere bakıp kendisi anlar. İstersen iki nokta ile açıkça yazabilirsin.

Temel tipler: Int (tam sayı), Float (ondalıklı), String (metin), Bool (doğru/yanlış).

nardene
let a = 42              // Int  (kendisi anladı)
let b = 3.14            // Float
let c = "merhaba"       // String
let d = true            // Bool

let e: Float = 10       // istersen açıkça yaz
let f: String = "elma"

print(a, b, c, d, e, f)
çıktı
42 3.14 merhaba true 10.0 elma

Sayılar

Tam sayı ile ondalıklı sayı birlikte kullanılabilir; sonuç ondalıklı olur.

Tek dikkat edilecek yer bölme: iki tam sayıyı bölersen sonuç tam sayıdır (ondalık kısım atılır). Ondalıklı sonuç istiyorsan taraflardan biri ondalıklı olmalı.

nardene
print(2 + 3)            // 5
print(10 - 4)           // 6
print(3 * 4)            // 12
print(7 / 2)            // 3    <- tam bölme
print(7 / 2.0)          // 3.5  <- ondalıklı
print(7 % 3)            // 1    <- kalan
print(1 + 2.5)          // 3.5  <- karışık kullanılabilir

print(sqrt(16), abs(-5), min(3, 8), max(3, 8))
print(floor(3.7), ceil(3.2), round(3.5))
çıktı
5
6
12
3
3.5
1
3.5
4.0 5 3 8
3 4 4

Metinler

Metinleri + ile birleştirebilirsin. Diğer taraf sayı ya da liste olsa bile çalışır — Nar onu otomatik yazıya çevirir.

Daha okunaklı yol: ${...} ile metnin içine doğrudan değer gömmek.

nardene
let ad = "Ayşe"
let yas = 30

print("Merhaba " + ad)
print("Yaş: " + yas)              // sayı otomatik yazıya döner
print("${ad} ${yas} yaşında")     // gömme — en okunaklısı
print("Toplam: ${yas + 5}")       // içinde işlem de yapılır
çıktı
Merhaba Ayşe
Yaş: 30
Ayşe 30 yaşında
Toplam: 35

Metin işlemleri

Metinlerin üzerinde nokta koyarak çağırabileceğin hazır işlemler var.

upperTr() ve lowerTr() Türkçeye özeldir: i harfini İ yapar, I harfini ı yapar. Normal upper()/lower() bunu bilmez.

nardene
let s = "  Merhaba Dünya  "

print(s.trim())                  // baştaki/sondaki boşlukları atar
print(s.trim().len())            // harf sayısı
print(s.trim().lower())
print("istanbul".upperTr())      // Türkçe: İSTANBUL
print("IĞDIR".lowerTr())         // Türkçe: ığdır
print("a-b-c".split("-"))
print("a-b-c".replace("-", "+"))
print("merhaba".contains("hab"))
print("merhaba".slice(0, 3))     // ilk 3 harf
print("ab".repeat(3))
çıktı
Merhaba Dünya
13
merhaba dünya
İSTANBUL
ığdır
["a", "b", "c"]
a+b+c
true
mer
ababab

Karakter kodları

Her karakterin bir sayı karşılığı vardır (Unicode kod noktası). kodu() karakterden sayıya, koddan(...) sayıdan karaktere çevirir.

Şifreleme, sıralama, kaçış dizisi çözme gibi işlerde gerekir. Emoji gibi geniş karakterler de tek parça sayılır.

nardene
print("A".kodu())          // 65
print("a".kodu())          // 97
print(koddan(65))          // A

// Türkçe harfler de aynı şekilde
print("ı".kodu())
print(koddan(305))

// Sezar şifresi: her harfi üç ileri kaydır
let harf = "d"
print(koddan(harf.kodu() + 3))
çıktı
65
97
A
305
ı
g

Doğru ve yanlış

Bool tipinin iki değeri vardır: true (doğru) ve false (yanlış). Karşılaştırmalar hep Bool üretir.
nardene
print(5 > 3)             // true
print(5 == 5)            // eşit mi
print(5 != 3)            // eşit değil mi
print(5 >= 5, 2 <= 1)

let a = true
let b = false
print(a && b)            // ve   — ikisi de doğruysa
print(a || b)            // veya — biri doğruysa
print(!a)                // değil
çıktı
true
true
true
true false
false
true
false

Karar vermek3 konu

Koşula göre farklı iş yapmak; if'i değer üretmek için kullanmak.

if / else

Bir koşul doğruysa bir bloğu, değilse başkasını çalıştırır. Birden çok durum için else if eklenir.
nardene
let not = 85

if not >= 90 {
  print("A aldın")
} else if not >= 80 {
  print("B aldın")
} else {
  print("Daha çok çalış")
}
çıktı
B aldın

if ile değer üretmek

if yalnızca bir şey yapmakla kalmaz, değer de üretebilir. Bir değişkene doğrudan atayabilirsin.

Bu biçimde else yazmak zorunludur — çünkü her durumda bir değer çıkması gerekir.

nardene
let yas = 20

let durum = if yas >= 18 { "yetişkin" } else { "çocuk" }
print(durum)

// zincirlenebilir
let not = 85
let harf = if not >= 90 { "A" } else if not >= 80 { "B" } else { "C" }
print("harf:", harf)

// başka bir ifadenin içinde de kullanılabilir
print("ücret: " + if yas < 12 { 0 } else { 50 })
çıktı
yetişkin
harf: B
ücret: 50

Dalda birden çok satır

Değer üreten if ve match dallarında tek satır yetmiyorsa blok yazabilirsin. Bloğun son satırı dalın değeridir.

Ara adımları ayrı bir fonksiyona çıkarmak zorunda değilsin; hesabı olduğu yerde yapabilirsin.

Son satır bir değer olmalı — atama ya da döngüyle biten bir dal hata verir; derleyici bunu söyler.

nardene
enum Hava {
  Gunesli
  Yagmurlu
  Karli
}

fn oneri(h: Hava) -> String = match h {
  Gunesli -> "şapka al"
  Yagmurlu -> {
    let arac = "şemsiye"
    let sebep = "yağmur"
    "${arac} al (${sebep})"
  }
  Karli -> {
    let katman = 3
    "${katman} kat giy"
  }
}

fn sinif(not: Int) -> String = if not >= 90 {
  "A"
} else if not >= 80 {
  let fark = 90 - not
  "B (A'ya ${fark} puan)"
} else {
  "C"
}

fn main() {
  print(oneri(Yagmurlu))
  print(oneri(Karli))
  print(sinif(85))
}
çıktı
şemsiye al (yağmur)
3 kat giy
B (A'ya 5 puan)

Tekrar etmek4 konu

Aynı işi tekrarlamak: for, while, break ve continue.

for döngüsü

Bir aralıktaki sayılar ya da bir listenin elemanları üzerinde döner.

0..5 son sayıyı içermez (0,1,2,3,4). İçermesini istersen 0..=5 yaz.

nardene
for i in 1..4 {
  print("sayı", i)
}

for i in 1..=3 {
  print("dahil", i)
}

for meyve in ["elma", "armut", "kiraz"] {
  print(meyve)
}

for harf in "abc" {
  print(harf)
}
çıktı
sayı 1
sayı 2
sayı 3
dahil 1
dahil 2
dahil 3
elma
armut
kiraz
a
b
c

Sıra numarasıyla dönmek

Listede sıra numarası da gerekiyorsa ikinci bir döngü değişkeni yaz. Elle sayaç tutmak gerekmez.

Metinde de çalışır ve indeks karakter sayar: “ç” tek karakterdir, iki değil.

Eşlemede iki değişken zaten anahtar ve değerdir. Aralıkta ikinci değişken yok — aralığın kendisi zaten sayı üretiyor.

nardene
let sehirler = ["İstanbul", "Ankara", "İzmir"]
for (sira, sehir) in sehirler {
  print("${sira + 1}. ${sehir}")
}

for (i, harf) in "açık" {
  print(i, harf)
}

let yaslar = {"Ayşe": 30, "Mehmet": 24}
for (ad, yas) in yaslar {
  print(ad, yas)
}
çıktı
1. İstanbul
2. Ankara
3. İzmir
0 a
1 ç
2 ı
3 k
Ayşe 30
Mehmet 24

while döngüsü

Koşul doğru olduğu sürece tekrarlar. Kaç kez döneceğini önceden bilmediğin durumlarda kullanılır.
nardene
var sayi = 1

while sayi < 100 {
  sayi = sayi * 2
}

print("100'ü aşan ilk ikinin kuvveti:", sayi)
çıktı
100'ü aşan ilk ikinin kuvveti: 128

break ve continue

break döngüyü tamamen bitirir. continue o turu atlayıp bir sonrakine geçer.
nardene
for i in 1..=10 {
  if i % 2 == 0 {
    continue          // çiftleri atla
  }
  if i > 7 {
    break             // 7'yi geçince dur
  }
  print(i)
}
çıktı
1
3
5
7

Listeler ve sözlükler4 konu

Sırayla duran veri ve anahtar–değer eşlemeleri.

Listeler

Liste, aynı tipte birden çok değeri sırayla tutar. Elemanlara sıfırdan başlayan numarayla (dizin) erişilir.

Olmayan bir dizine erişirsen program net bir hata mesajıyla durur — sessizce yanlış sonuç vermez.

nardene
var sayilar = [5, 3, 8, 1]

print(sayilar)
print("ilk eleman:", sayilar[0])
print("kaç eleman:", sayilar.len())

sayilar.push(10)              // sona ekle
print(sayilar)

print("sıralı:", sayilar.sort())
print("ters:", sayilar.reverse())
print("içinde 8 var mı:", sayilar.contains(8))
print("bir kısmı:", sayilar.slice(1, 3))
çıktı
[5, 3, 8, 1]
ilk eleman: 5
kaç eleman: 4
[5, 3, 8, 1, 10]
sıralı: [1, 3, 5, 8, 10]
ters: [10, 1, 8, 3, 5]
içinde 8 var mı: true
bir kısmı: [3, 8]

Listelerle kolay işlemler

Bir listeyle en çok yapılan işler için adı kendini anlatan hazır işlemler var. Hiçbirinde fonksiyon yazman gerekmez.

Toplama, ortalama, filtreleme gibi şeyler için önce buraya bak; aradığın yoksa bir sonraki başlıktaki map/filter yöntemini kullanırsın.

nardene
let sayilar = [5, 3, 8, 1, 3]

print("toplam:", sayilar.toplam())
print("ortalama:", sayilar.ortalama())
print("çarpım:", sayilar.carpim())
print("en büyük:", sayilar.enBuyuk() ?? 0)
print("en küçük:", sayilar.enKucuk() ?? 0)

print("iki katları:", sayilar.kat(2))
print("10 artırılmış:", sayilar.artir(10))
print("3'ten büyükler:", sayilar.buyukler(3))
print("3'ten küçükler:", sayilar.kucukler(3))
print("çiftler:", sayilar.ciftler())
print("tekler:", sayilar.tekler())

print("benzersiz:", sayilar.benzersiz())
print("kaç tane 3 var:", sayilar.say(3))

let kelimeler = ["nar", "elma", "kiraz"]
print("büyük harf:", kelimeler.buyukHarf())
print("'a' içerenler:", kelimeler.icerenler("a"))
çıktı
toplam: 20
ortalama: 4.0
çarpım: 360
en büyük: 8
en küçük: 1
iki katları: [10, 6, 16, 2, 6]
10 artırılmış: [15, 13, 18, 11, 13]
3'ten büyükler: [5, 8]
3'ten küçükler: [1]
çiftler: [8]
tekler: [5, 3, 1, 3]
benzersiz: [5, 3, 8, 1]
kaç tane 3 var: 2
büyük harf: ["NAR", "ELMA", "KİRAZ"]
'a' içerenler: ["nar", "elma", "kiraz"]

map, filter, reduce

Bir önceki başlıktaki hazır işlemler aradığını karşılamıyorsa, kendi kuralını yazabilirsin. Bunlar listeyi tek satırda dönüştürmenin genel yoludur.

|x| ... yazımı "her eleman için" demektir; x sırayla her elemanın yerini alır.

  • map — her elemanı dönüştürür
  • filter — koşula uyanları seçer
  • reduce — hepsini tek değere indirger
nardene
let sayilar = [1, 2, 3, 4, 5]

print(sayilar.map(|x| x * x))          // her birinin karesi
print(sayilar.filter(|x| x % 2 == 0))  // sadece çiftler
print(sayilar.reduce(|a, b| a + b, 0)) // toplamı

// zincirlenebilir
print(sayilar.filter(|x| x > 2).map(|x| x * 10))

let kelimeler = ["nar", "elma", "kiraz"]
print(kelimeler.map(|k| k.upperTr()).join(", "))
çıktı
[1, 4, 9, 16, 25]
[2, 4]
15
[30, 40, 50]
NAR, ELMA, KİRAZ

Sözlükler

Sözlük, bir anahtara karşılık bir değer tutar — "elma" için 12 gibi.

get her zaman "olmayabilir" bir sonuç verir; bu yüzden ?? varsayılan ile bir yedek değer belirtirsin. Bu, olmayan anahtarı unutmanı engeller.

nardene
var stok = {"elma": 12, "armut": 3}

stok.set("kiraz", 7)

print(stok)
print("kaç çeşit:", stok.len())
print("elma:", stok.get("elma") ?? 0)
print("muz:", stok.get("muz") ?? 0)      // yok -> 0
print("armut var mı:", stok.has("armut"))

for (urun, adet) in stok {
  print("${urun}: ${adet} adet")
}
çıktı
{"elma": 12, "armut": 3, "kiraz": 7}
kaç çeşit: 3
elma: 12
muz: 0
armut var mı: true
elma: 12 adet
armut: 3 adet
kiraz: 7 adet

Fonksiyonlar4 konu

İşi ada bağlamak, parçalara ayırmak, isimsiz fonksiyonlar.

Fonksiyon tanımlamak

Fonksiyon, bir işi bir isim altında toplar; sonra o ismi çağırırsın.

Parametrelerin ve döndürdüğü değerin tipini yazman gerekir. Bu fazladan yazı gibi görünür ama karşılığında derleyici seni yanlış kullanımdan korur ve hata mesajları çok daha anlaşılır olur.

nardene
fn selamla(ad: String) {
  print("Merhaba " + ad)
}

fn topla(a: Int, b: Int) -> Int {
  return a + b
}

fn main() {
  selamla("Nar")
  print(topla(3, 4))
}
çıktı
Merhaba Nar
7

Tek satırlık fonksiyon

Fonksiyonun gövdesi tek bir ifadeyse süslü parantez yerine = kullanabilirsin. Daha kısa ve okunaklı olur.
nardene
fn kare(x: Int) -> Int = x * x

fn selam(ad: String) -> String = "Merhaba " + ad

fn buyuk_mu(n: Int) -> Bool = n > 100

fn main() {
  print(kare(9))
  print(selam("Nar"))
  print(buyuk_mu(150))
}
çıktı
81
Merhaba Nar
true

Kendini çağıran fonksiyon

Bir fonksiyon kendini çağırabilir. Bunun için mutlaka bir "durma noktası" olmalı, yoksa sonsuza kadar devam eder.
nardene
fn faktoriyel(n: Int) -> Int {
  if n <= 1 {
    return 1              // durma noktası
  }
  return n * faktoriyel(n - 1)
}

fn fib(n: Int) -> Int = if n < 2 { n } else { fib(n - 1) + fib(n - 2) }

fn main() {
  print("6! =", faktoriyel(6))
  print("fib(20) =", fib(20))
}
çıktı
6! = 720
fib(20) = 6765

İsimsiz fonksiyonlar

Küçük, tek kullanımlık fonksiyonları isim vermeden yazabilirsin. map ve filter içinde gördüğün |x| ... tam olarak budur.

Bir değişkene atarsan parametrenin tipini yazman gerekir — çünkü ortada tipi anlamayı sağlayacak bir bağlam yoktur.

nardene
let iki_kat = |x: Int| x * 2
print(iki_kat(21))

// birden çok satır gerekiyorsa
let mesafe = |a: Int, b: Int| {
  let fark = a - b
  return abs(fark)
}
print(mesafe(3, 10))

// bir listeye uygularken tip yazmaya gerek yok
print([1, 2, 3].map(|x| x + 100))
çıktı
42
7
[101, 102, 103]

Kendi tiplerini yapmak10 konu

Kendi veri biçimlerini kurmak: struct, enum, metot, match.

struct — birbirine ait bilgiler

Birlikte anlam ifade eden bilgileri tek bir tip altında toplar. Bir kişinin adı ve yaşı gibi.

Alanlar varsayılan olarak değişmezdir. Değişebilmesi için başına var yaz.

nardene
struct Kisi {
  ad: String
  var yas: Int
}

fn main() {
  let k = Kisi { ad: "Ayşe", yas: 30 }

  print(k)
  print(k.ad, "->", k.yas)

  k.yas = 31        // 'var' olduğu için değiştirilebilir
  print("doğum günü sonrası:", k.yas)
}
çıktı
Kisi { ad: "Ayşe", yas: 30 }
Ayşe -> 30
doğum günü sonrası: 31

Tipe ait fonksiyonlar (metotlar)

Bir tipin içine, o tiple ilgili fonksiyonlar koyabilirsin. Bunlara metot denir. İçeride self, üzerinde çalıştığın nesneyi gösterir.
nardene
struct Dikdortgen {
  en: Float
  boy: Float

  fn alan() -> Float = self.en * self.boy

  fn cevre() -> Float = 2.0 * (self.en + self.boy)

  fn kare_mi() -> Bool = self.en == self.boy

  fn buyut(kat: Float) -> Dikdortgen {
    return Dikdortgen { en: self.en * kat, boy: self.boy * kat }
  }
}

fn main() {
  let d = Dikdortgen { en: 3.0, boy: 4.0 }
  print("alan:", d.alan())
  print("çevre:", d.cevre())
  print("kare mi:", d.kare_mi())
  print("iki katı:", d.buyut(2.0))
}
çıktı
alan: 12.0
çevre: 14.0
kare mi: false
iki katı: Dikdortgen { en: 6.0, boy: 8.0 }

enum — sınırlı seçenekler

Bir değerin alabileceği seçenekler belliyse enum kullanılır: hava durumu, sipariş durumu, ödeme tipi gibi.

Metin kullanmaya göre avantajı: yazım hatası yapamazsın ve derleyici seçeneklerin hepsini ele aldığından emin olur.

nardene
enum Hava {
  Gunesli
  Yagmurlu
  Karli
}

fn main() {
  let bugun = Hava.Yagmurlu
  print(bugun)
}
çıktı
Hava.Yagmurlu

Enum adını yazmadan

Hava.Yagmurlu yerine kısaca Yagmurlu yazabilirsin. Seçenek adı programda tek bir enum'a aitse derleyici hangisini kastettiğini bilir.

Aynı ad iki ayrı enum'da geçiyorsa derleyici sana söyler ve hangisi olduğunu yazmanı ister — sessizce yanlış olanı seçmez.

nardene
enum Hava {
  Gunesli
  Yagmurlu
  Karli
}

fn oneri(h: Hava) -> String = match h {
  Gunesli -> "şapka al"
  Yagmurlu -> "şemsiye al"
  Karli -> "atkı al"
}

fn main() {
  print(oneri(Yagmurlu))
  print(oneri(Hava.Karli))   // uzun yazım da geçerli
}
çıktı
şemsiye al
atkı al

Dilim, yayma ve boru

Üç kısaltma, üçü de sık gereken işler için.

Dilim a[1..3] listenin ya da metnin bir parçasını alır; ..= üst sınırı da içine katar. Sınırlar uçları aşarsa kırpılır, ters verilirse boş döner — yani dilim almak programı durdurmaz.

Yayma [...a, 3] bir listenin öğelerini yenisinin içine serer. Kaynak liste değişmez.

Boru x |> f(a) değeri ilk argüman yaparak f(x, a) çağırır. İç içe çağrıları soldan sağa okunan bir zincire çevirir: sar(buyut(temizle(s))) yerine s |> temizle |> buyut |> sar.

nardene
fn temizle(s: String) -> String = s.trim()
fn buyut(s: String) -> String = s.upperTr()
fn sar(s: String, k: String) -> String = k + s + k

fn main() {
  let sayilar = [1, 2, 3, 4, 5]
  print(sayilar[1..3])        // [2, 3]
  print(sayilar[1..=3])       // [2, 3, 4]
  print("merhaba"[0..3])      // mer

  let bas = [0]
  print([...bas, ...sayilar[0..2], 9])

  print("  merhaba  " |> temizle |> buyut |> sar("*"))
}
çıktı
[2, 3]
[2, 3, 4]
mer
[0, 1, 2, 9]
*MERHABA*

Tuple — iki değeri birlikte döndürmek

Bir fonksiyon tek değer döndürür; iki şey döndürmek gerektiğinde eskiden bunun için bir struct açmak gerekirdi. Tuple tam bu boşluğu doldurur: parantez içinde birkaç değer, alan adı yok, sırayla okunur.

let (a, b) = ... ile tuple doğrudan adlarına açılır; öğeye tek tek .0, .1 ile de erişilebilir.

Ne zaman struct'a geçmeli: alanların adı anlam taşımaya başladığında. (Int, Int) okunurken hangisinin bölüm hangisinin kalan olduğu belli değilse, orası artık bir struct yeridir — dört öğeyi geçen tuple için derleyici de uyarır.

nardene
fn bolVeKalan(a: Int, b: Int) -> (Int, Int) = (a / b, a % b)

fn enUzunKelime(cumle: String) -> (String, Int) {
  var enUzun = ""
  for k in cumle.split(" ") {
    if k.len() > enUzun.len() {
      enUzun = k
    }
  }
  return (enUzun, enUzun.len())
}

fn main() {
  let (bolum, kalan) = bolVeKalan(17, 5)
  print(str(bolum) + " kalan " + str(kalan))

  let (kelime, uzunluk) = enUzunKelime("bugün hava çok güzel")
  print(kelime + " — " + str(uzunluk) + " harf")

  // Tek tek de okunur:
  let ikili = bolVeKalan(9, 2)
  print(str(ikili.0) + "/" + str(ikili.1))
}
çıktı
3 kalan 2
bugün — 5 harf
4/1

Koşullu kol ve aralık — if zincirini kısaltmak

Bir match kolu yalnız desene değil, bir koşula da bakabilir: desen if koşul ->. Desenin bağladığı ad koşulun içinde kullanılabilir.

Sayı ve metin aralıkları da desen olur: 1..=9 ikisini de içine alır, 0..3 üst ucu dışarıda bırakır.

Neden önemli: peş peşe if yazmak yerine bütün durumlar tek yerde, alt alta durur. Derleyici de bir durumu atladığını söyleyebilir — koşullu kol tek başına match'i tamamlamış saymaz, çünkü koşul tutmayabilir.

nardene
fn siniflandir(n: Int) -> String = match n {
  x if x < 0 -> "eksi"
  0 -> "sıfır"
  1..=9 -> "tek haneli"
  10..=99 -> "iki haneli"
  _ -> "daha büyük"
}

fn main() {
  for n in [-3, 0, 7, 42, 1000] {
    print(str(n) + " -> " + siniflandir(n))
  }
}
çıktı
-3 -> eksi
0 -> sıfır
7 -> tek haneli
42 -> iki haneli
1000 -> daha büyük

Varsayılan değer ve ad ile argüman

Bir parametreye varsayılan verirsen çağıran onu yazmayabilir. Argümanı adıyla da verebilirsin; o zaman sıra önemli olmaz.

Aynısı struct alanları için de geçerli: varsayılanı olan alan kurulurken yazılmayabilir. Sekiz alanlı bir yapıyı kurmak için sekizini birden yazmak gerekmez.

Kural: varsayılanı olmayan parametre, varsayılanı olanlardan sonra gelemez — gelirse çağrıda ona ulaşılamazdı.

nardene
struct Kutu {
  var yazi: String
  var genislik: Int = 20
  var cerceve: Bool = true
}

fn bicim(metin: String, once: String = "[", sonra: String = "]") -> String =
  once + metin + sonra

fn main() {
  print(bicim("selam"))
  print(bicim("selam", sonra: ">"))
  print(bicim(sonra: ">", once: "<", metin: "selam"))

  let k = Kutu { yazi: "not" }
  print(k.yazi + " " + str(k.genislik) + " " + str(k.cerceve))
}
çıktı
[selam]
[selam>
<selam>
not 20 true

match — seçeneklere göre davranmak

match, bir değerin hangi durumda olduğuna bakıp ona göre iş yapar.

En değerli özelliği: bir seçeneği ele almayı unutursan program çalışmaz, derleyici sana hangisini unuttuğunu söyler. Sonradan enum'a yeni bir seçenek eklediğinde, düzeltmen gereken bütün yerleri derleyici gösterir.

nardene
enum Hava {
  Gunesli
  Yagmurlu
  Karli
}

fn tavsiye(h: Hava) -> String = match h {
  Hava.Gunesli -> "Şapka al."
  Hava.Yagmurlu -> "Şemsiye al."
  Hava.Karli -> "Mont al."
}

fn main() {
  for h in [Hava.Gunesli, Hava.Yagmurlu, Hava.Karli] {
    print(tavsiye(h))
  }

  // sayı ya da metin üzerinde de çalışır
  let n = 3
  print(match n {
    1 -> "bir"
    2 -> "iki"
    _ -> "çok"           // _ = geri kalan her şey
  })
}
çıktı
Şapka al.
Şemsiye al.
Mont al.
çok

Veri taşıyan seçenekler

Seçenekler yanlarında veri taşıyabilir. Bu, "işlem başarılı oldu ve sonucu şu" ya da "hata oldu ve sebebi şu" gibi durumları anlatmanın temiz yoludur.

Nar'da hatalar bu şekilde yönetilir. Hatayı görmezden gelemezsin, çünkü sonucu almak için match yazmak zorundasın.

nardene
enum Sonuc {
  Tamam(Int)
  Hata(String)
}

fn bol(a: Int, b: Int) -> Sonuc {
  if b == 0 {
    return Sonuc.Hata("sıfıra bölünemez")
  }
  return Sonuc.Tamam(a / b)
}

fn main() {
  for bolen in [4, 0, 5] {
    match bol(100, bolen) {
      Sonuc.Tamam(deger) -> print("100 / ${bolen} = ${deger}")
      Sonuc.Hata(mesaj) -> print("olmadı: ${mesaj}")
    }
  }
}
çıktı
100 / 4 = 25
olmadı: sıfıra bölünemez
100 / 5 = 20

Her tiple çalışan kod4 konu

Aynı kodu her tiple çalıştırmak.

Tip parametreleri

Bazen bir yapı ya da fonksiyon, içindeki değerin ne olduğuna bakmadan çalışır: bir kutu hem sayı hem metin taşıyabilir.

Böyle durumlarda tipe bir parametre verilir. Aşağıdaki T "herhangi bir tip" demektir; kullanırken hangisi olduğu belli olur.

Faydası: aynı kodu her tip için yeniden yazmazsın, ama tip güvenliği kaybolmaz — Kutu<Int> içine metin koyamazsın.

nardene
struct Kutu<T> {
  deger: T
  fn al() -> T = self.deger
}

fn main() {
  let sayiKutusu = Kutu { deger: 5 }
  let metinKutusu = Kutu { deger: "merhaba" }

  print(sayiKutusu.al())
  print(metinKutusu.al())

  // Tipi açıkça da yazabilirsin
  let acik = Kutu<Float> { deger: 2.5 }
  print(acik.deger)
}
çıktı
5
merhaba
2.5

Her tiple çalışan fonksiyonlar

Fonksiyonlar da tip parametresi alabilir. Tip, verdiğin değerden kendiliğinden anlaşılır — yazmana gerek yoktur.
nardene
fn ilk<T>(liste: [T]) -> T? {
  if liste.len() == 0 {
    return none
  }
  return liste[0]
}

fn ikiKat<T>(x: T) -> [T] = [x, x]

fn main() {
  print(ilk([3, 4, 5]) ?? 0)
  print(ilk(["a", "b"]) ?? "yok")

  let bos: [Int] = []
  print(ilk(bos) ?? -1)

  print(ikiKat(7))
  print(ikiKat("x"))
}
çıktı
3
a
-1
[7, 7]
["x", "x"]

Hata yönetimi: Sonuç tipi

Nar'da hata fırlatma (try/catch) yoktur. Bir işlem başarısız olabiliyorsa bunu dönüş tipinde söyler.

Çağıran match yazmak zorunda kaldığı için hatayı görmezden gelemez. Unutursan program çalışmaz — derleyici hangi durumu ele almadığını söyler.

Hazır tanım araclar/sonuc.nar dosyasındadır; kendin de yazabilirsin, çünkü sıradan bir enum'dur.

nardene
enum Sonuc<T, H> {
  Tamam(T)
  Hata(H)
}

fn bol(a: Int, b: Int) -> Sonuc<Int, String> {
  if b == 0 {
    return Sonuc.Hata("sıfıra bölünemez")
  }
  return Sonuc.Tamam(a / b)
}

fn main() {
  for bolen in [4, 0] {
    match bol(100, bolen) {
      Sonuc.Tamam(deger) -> print("100 / ${bolen} = ${deger}")
      Sonuc.Hata(mesaj) -> print("olmadı: ${mesaj}")
    }
  }
}
çıktı
100 / 4 = 25
olmadı: sıfıra bölünemez

Kendine başvuran tipler

Bir tip kendi içinde yine kendini taşıyabilir. Bağlı liste ve ağaç gibi yapılar böyle kurulur.
nardene
enum Agac<T> {
  Yaprak(T)
  Dal([Agac<T>])
}

fn topla(a: Agac<Int>) -> Int = match a {
  Agac.Yaprak(n) -> n
  Agac.Dal(dallar) -> dallar.map(|d| topla(d)).toplam()
}

fn main() {
  let agac = Agac.Dal([
    Agac.Yaprak(1),
    Agac.Dal([Agac.Yaprak(2), Agac.Yaprak(3)])
  ])
  print("yaprakların toplamı:", topla(agac))
}
çıktı
yaprakların toplamı: 6

Ortak davranış: arayüzler3 konu

Farklı tiplere ortak davranış tanımlamak.

interface

Farklı tipler aynı işi yapabiliyorsa, bu ortak davranışa bir ad verilir. interface yalnızca hangi metotların bulunması gerektiğini söyler; nasıl yapılacağını her tip kendi bilir.

Bir tip arayüzü bildiriminde üstlenir (struct Nokta: Yazdirilabilir). Bu bir sözdür: metotlardan biri eksikse ya da imzası tutmuyorsa program derlenmez.

nardene
interface Yazdirilabilir {
  fn yaz() -> String
}

struct Nokta: Yazdirilabilir {
  x: Float
  y: Float
  fn yaz() -> String = "(${self.x}, ${self.y})"
}

struct Kisi: Yazdirilabilir {
  ad: String
  fn yaz() -> String = "kişi: " + self.ad
}

fn goster(sey: Yazdirilabilir) {
  print(sey.yaz())
}

fn main() {
  goster(Nokta { x: 1.0, y: 2.0 })
  goster(Kisi { ad: "Ayşe" })
}
çıktı
(1.0, 2.0)
kişi: Ayşe

Farklı tipleri bir arada tutmak

Bir liste, aynı arayüzü üstlenen farklı tipleri bir arada tutabilir. enum da arayüz üstlenebilir.
nardene
interface Yazdirilabilir {
  fn yaz() -> String
}

struct Kisi: Yazdirilabilir {
  ad: String
  fn yaz() -> String = "kişi: " + self.ad
}

enum Durum: Yazdirilabilir {
  Acik
  Kapali
  fn yaz() -> String = match self {
    Durum.Acik -> "açık"
    Durum.Kapali -> "kapalı"
  }
}

fn main() {
  let hepsi: [Yazdirilabilir] = [
    Kisi { ad: "Ayşe" },
    Durum.Acik,
    Durum.Kapali
  ]
  for e in hepsi {
    print("- " + e.yaz())
  }
}
çıktı
- kişi: Ayşe
- açık
- kapalı

Tip parametresine sınır koymak

Generic bir fonksiyonda tip parametresine sınır konabilir: <T: Olculebilir> "T her tip olabilir, yeter ki olcu metodu olsun" demektir.

Böylece fonksiyon içinde o metodu çağırabilirsin; sınır koymadığın metotları kullanmana derleyici izin vermez.

nardene
interface Olculebilir {
  fn olcu() -> Float
}

struct Nokta: Olculebilir {
  x: Float
  y: Float
  fn olcu() -> Float = sqrt(self.x * self.x + self.y * self.y)
}

fn enBuyuk<T: Olculebilir>(liste: [T]) -> Float {
  var en = 0.0
  for e in liste {
    if e.olcu() > en {
      en = e.olcu()
    }
  }
  return en
}

fn main() {
  print(enBuyuk([
    Nokta { x: 3.0, y: 4.0 },
    Nokta { x: 6.0, y: 8.0 }
  ]))
}
çıktı
10.0

Olmayabilen değerler5 konu

Olmayabilen değeri tipin kendisinde taşımak.

none ve soru işareti

Bazı değerler olmayabilir: aranan kayıt bulunamamıştır, kullanıcı takma ad girmemiştir. Nar'da bu durum tipte açıkça görünür — sonuna ? koyarsın.

Bu, birçok dilde programların en sık çöktüğü hatayı ("null" hatası) baştan engeller: olmayabilecek bir değeri kontrol etmeden kullanamazsın.

nardene
let takma: String? = none
let gercek: String? = "Nar"

print(takma)
print(gercek)

// ?? ile yedek değer ver
print(takma ?? "takma adı yok")
print(gercek ?? "takma adı yok")
çıktı
none
Nar
takma adı yok
Nar

Değeri güvenle kullanmak

Olmayabilen bir değeri kullanmanın üç yolu var:
  • ?? yedek — yoksa şunu kullan
  • if x != none { ... } — kontrol et, içeride normal kullan
  • x! — "kesinlikle var" de; yanılırsan program net bir hatayla durur
nardene
let sayilar = [10, 20, 30]

let ilk = sayilar.first()        // İlk eleman olmayabilir (liste boşsa)
print("ilk:", ilk ?? 0)

if ilk != none {
  print("var, iki katı:", ilk * 2)   // burada artık kesin var
}

var bos: [Int] = []
print("boş listenin ilki:", bos.first() ?? -1)
çıktı
ilk: 10
var, iki katı: 20
boş listenin ilki: -1

if let — açarak dallanmak

Olmayabilen bir değeri açıp aynı anda dallanmak için if let. Değer none değilse ada bağlanır ve o dalda açılmış hâliyle görünür; ! yazmak gerekmez.

Bu olmadan önce bir değişkene almak, sonra sorup sonra açmak gerekirdi:

let e = bul("#uygulama")
if e != none {
  e!.ekle(baslik)
}

else dalında ad görünmez: orada değer zaten yoktur. else if let ile zincirlenebilir.

nardene
fn yas(ad: String) -> Int? {
  let defter = {"Ayşe": 30, "Mehmet": 24}
  return defter.get(ad)
}

fn main() {
  if let y = yas("Ayşe") {
    print("Ayşe ${y} yaşında")
  } else {
    print("Ayşe defterde yok")
  }

  if let y = yas("Zeynep") {
    print("Zeynep ${y} yaşında")
  } else if let y = yas("Mehmet") {
    print("Zeynep yok ama Mehmet ${y} yaşında")
  } else {
    print("ikisi de yok")
  }
}
çıktı
Ayşe 30 yaşında
Zeynep yok ama Mehmet 24 yaşında

Nar zaten daraltma yapar: if x != none içinde x açılmış sayılır. if let bunu bir adım öteye taşır — değer bir çağrıdan geliyorsa önce değişkene almak gerekmez.

? — gelmediyse ben de gelmedim

Olmayabilen değerlerle çalışan kodun büyük kısmı aynı cümledir: gelmediyse ben de gelmedim. ? tam bunu yazar — değer none ise fonksiyondan hemen none döner, değilse açılmış değeri verir.

Bu olmadan her adım dört satırdı:

let satir = satirlar.first()
if satir == none {
  return none
}
let sayi = int(satir!.trim())
if sayi == none {
  return none
}
return sayi

? fonksiyondan erken çıkar, bu yüzden iki koşulu var: değerin opsiyonel olması ve bulunduğu fonksiyonun opsiyonel döndürmesi. İkisi de sağlanmıyorsa derleyici söyler.

Erken çıkışın konabileceği bir yer olmayan yerlerde ? kullanılamaz: ??, && ve || işleçlerinin sağında, değer üreten ifin dallarında, match kollarında, while koşulunda ve tek ifadelik lambda gövdesinde. Bu yerlerde sessizce yanlış çalışmak yerine açık bir hata alırsın; değeri önce bir değişkene al. Gövdeli lambdada serbesttir — orada ? lambdadan çıkar.

nardene
// Satırların ilkindeki sayıyı okur.
// Herhangi bir adım tutmazsa sonuç none olur.
fn ilkSayi(satirlar: [String]) -> Int? {
  let satir = satirlar.first()?
  return int(satir.trim())?
}

fn main() {
  print(ilkSayi(["  42  ", "başka satır"]) ?? -1)
  print(ilkSayi(["sayı değil"]) ?? -1)
  print(ilkSayi([]) ?? -1)
}
çıktı
42
-1
-1

Değer bir kez hesaplanır: f()? çağrıyı iki kez yapmaz. Üretilen JavaScript de elle yazılmış gibi durur — geçici bir değişken ve bir if.

Güvenli erişim: ?.

Olmayabilen bir değerin içine erişirken ?. kullanılır. Değer yoksa zincir durur ve sonuç none olur — program çökmez.
nardene
struct Adres {
  sehir: String
}

struct Kisi {
  ad: String
  adres: Adres?
}

fn main() {
  let a = Kisi { ad: "Ayşe", adres: Adres { sehir: "Ankara" } }
  let b = Kisi { ad: "Veli", adres: none }

  print(a.adres?.sehir ?? "adres yok")
  print(b.adres?.sehir ?? "adres yok")
}
çıktı
Ankara
adres yok

Web sayfası yapmak3 konu

Tarayıcıda çalışan sayfa üretmek.

Sayfayla konuşmak

Nar programı tarayıcıda çalışırken sayfadaki öğelere erişebilir: yazı değiştirebilir, düğme ekleyebilir, tıklamaları dinleyebilir.

bul(...) bir öğeyi seçer. Öğe olmayabileceği için sonuç her zaman olabilir tipindedir — kontrol etmen gerekir. Bu, "öğe yok" hatasını baştan engeller.

Programı tarayıcıda çalışacak biçimde derlemek için: nar build program.nar --target web

nar
fn main() {
  let baslik = bul("#baslik")

  if baslik != none {
    baslik.metinYaz("Merhaba Nar")
    baslik.stil("color", "#a32a2f")
    baslik.sinifEkle("vurgulu")
  }
}

Düğme ve tıklama

olustur(...) yeni bir öğe yapar, ekle(...) onu sayfaya koyar, dinle(...) ise tıklama gibi olayları yakalar.

Aşağıdaki program çalışan bir sayaçtır; tam hâli ornekler/sayac_web.nar dosyasında.

nar
var sayi = 0

fn main() {
  let alan = bul("#uygulama")
  if alan == none {
    print("bu program tarayıcıda çalışmalı")
    return
  }

  let ekran = olustur("div")
  ekran.ozellikYaz("id", "ekran")
  ekran.metinYaz("0")
  alan!.ekle(ekran)

  let dugme = olustur("button")
  dugme.metinYaz("Artır")
  dugme.dinle("click", || {
    sayi += 1
    bul("#ekran")?.metinYaz(str(sayi))
  })
  alan!.ekle(dugme)
}

Öğe üzerinde yapabileceklerin

Bir öğeyi bulduktan sonra kullanabileceğin işlemler. Hepsi Element tipinin üzerindedir.

Not: Bu işlemler yalnızca tarayıcıda anlamlıdır. Aynı programı nar run ile çalıştırırsan bul(...) her zaman none döner ve program çökmez — bu sayede aynı kodu her iki ortamda da güvenle çalıştırabilirsin.

nar
fn ornekler(e: Element) {
  e.metinYaz("yazı")            // içindeki yazıyı değiştir
  print(e.metin())              // içindeki yazıyı oku
  e.htmlYaz("<b>kalın</b>")     // HTML olarak yaz
  e.degerYaz("giriş")           // input/textarea değeri
  print(e.deger())

  e.sinifEkle("acik")
  e.sinifSil("kapali")
  print(e.sinifVarMi("acik"))

  e.ozellikYaz("id", "kutu")
  print(e.ozellik("id") ?? "yok")
  e.stil("color", "red")

  e.dinle("click", || { print("tıklandı") })

  e.ekle(olustur("span"))       // çocuk ekle
  e.temizle()                   // içini boşalt
  e.odaklan()

  print(e.bul(".ic") != none)   // içinde ara
}

Uygulama arayüzü7 konu

Durumdan görünüm üreten bildirimsel arayüz katmanı.

Ekranı tarif etmek

Bir önceki bölümde öğeleri tek tek oluşturup sayfaya ekledik. Uygulama büyüdükçe bu yorucu olur: bir şey değişince hangi öğeyi güncelleyeceğini takip etmen gerekir.

araclar/arayuz.nar kütüphanesi bunu tersine çevirir: ekranı nasıl kuracağını değil, neye benzemesi gerektiğini yazarsın. Durum değişince kütüphane ekranı kendisi yeniler.

Bu kütüphane Nar'ın kendisiyle yazılmıştır — dilin bir parçası değil, dille yazılmış sıradan bir dosyadır. İstersen kopyalayıp kendine göre değiştirebilirsin.

nar
import "araclar/arayuz.nar"

// Ekran bir Gorunum ağacıdır: Sutun, Satir, Kutu içinde
// Metin, Baslik, Dugme, Giris, Bosluk, Cizgi.
fn ciz(sayi: Int) -> Gorunum = Kutu([
  Baslik("Sayaç"),
  Metin("değer: ${sayi}"),
  Satir([
    Dugme("azalt", || { azalt() }),
    Dugme("artır", || { artir() })
  ])
])

Durum ve otomatik yenileme

uygulamaBaslat üç şey ister: çizimin yapılacağı yer, başlangıç durumu ve durumu görünüme çeviren fonksiyon.

Durumu degistir ile güncellersin; ekranı yenilemek senin işin değildir. Durum her tip olabilir — sayı, metin, kendi struct'ın ya da bunların listesi.

nar
import "araclar/arayuz.nar"

var uygulama: Uygulama<Int>? = none

fn artir() {
  if uygulama != none {
    uygulama!.degistir(uygulama!.durum + 1)
  }
}

fn ciz(sayi: Int) -> Gorunum = Sutun([
  Baslik("Sayaç"),
  Metin("değer: ${sayi}"),
  Dugme("artır", || { artir() })
])

fn main() {
  // Sayfadaki <div id="uygulama"></div> içine çizer.
  uygulama = uygulamaBaslat("#uygulama", 0, ciz)
}

Örnek: yapılacaklar listesi

Görünümü üreten kod sıradan Nar kodudur: if, for, liste işlemleri hepsi kullanılabilir. Aşağıdaki parça, listeyi görünüme çeviren kısımdır.

Tamamı ornekler/yapilacaklar_uygulamasi.nar dosyasındadır. Masaüstü uygulaması olarak paketlemek için: nar build ornekler/yapilacaklar_uygulamasi.nar --target masaustu

nar
fn gorevSatiri(sira: Int, g: Gorev) -> Gorunum {
  var kutu = "[ ]"
  if g.bitti {
    kutu = "[x]"
  }
  return Satir([
    Dugme(kutu, || { gorevDegistir(sira) }),
    Metin(g.baslik)
  ])
}

fn gorevListesi(gorevler: [Gorev]) -> Gorunum {
  if gorevler.len() == 0 {
    return Metin("Henüz görev yok.")
  }
  var satirlar: [Gorunum] = []
  var sira = 0
  for g in gorevler {
    satirlar.push(gorevSatiri(sira, g))
    sira += 1
  }
  return Sutun(satirlar)
}

Deriler: aynı bileşenler, başka biçim

Tema rengi seçer (açık / koyu), deri biçimi: yarıçap, boşluk, gölge ve hareket. İkisi birbirinden bağımsızdır — yumuşak deri hem açık hem koyu temada çalışır.

Şu an iki deri var: varsayılan ve yumusak. Yumuşak deri düğmeleri hap biçimine sokar, kartları yuvarlatıp yükseltir, girdileri kenarlık yerine yumuşak bir yuvaya oturtur ve boşlukları açar.

Deri paleti değiştirmez. Kenarları zeminle aynı renge çeken kabartma yaklaşımı yumuşak görünür ama kontrast eşiğini geçemez; buradaki yumuşaklık renkten değil biçimden gelir, o yüzden okunabilirlik her deride aynı kalır.

nar
import "../araclar/arayuz.nar"

fn ciz(n: Int) -> Gorunum = Kart("Deri denemesi", [
  Metin("Sayaç: ${n}"),
  Satir([
    Dugme("Artır", || { }),
    IkincilDugme("Sıfırla", || { })
  ])
])

fn main() {
  deriSec("yumusak")        // "" verirsen varsayılana döner
  uygulamaBaslat("#uygulama", 0, ciz)
}

Deri ayrı bir stil öğesinde durur; durum değişip ekran yeniden çizildiğinde bozulmaz. Seçili deriyi deri() ile okuyabilirsin.

Hareket: anlam taşıyan animasyon

Hareket anlam taşımalı. Taşımıyorsa hem gereksizdir hem de erişilebilirlik borcu yaratır. Kütüphane bu yüzden keyfi bir tween motoru değil, adı olan az sayıda hareket sunar.

Her şey CSS ile yapılır; Nar yalnızca sınıf ekler. Bunun üç sonucu var: hareket birleştirici üzerinde yürür, kullanıcı araya girdiğinde takılı kalan durum olmaz (doğruluk hiçbir zaman animationend olayına bağlı değildir), ve prefers-reduced-motion açıkken hareket tamamen kapanıp öğe nihai okunabilir durumunda görünür — yalnız süre kısaltılmaz.

Giriş hareketleri öğe ilk kez göründüğünde bir kez oynar. Durum her değiştiğinde ağaç baştan çizilir; hareket her çizimde oynasaydı bir giriş alanına yazan kişi her tuşta bütün ekranın yeniden süzülmesini izlerdi. Sekme ya da sayfa değiştirirken hareketDefteriniSil() çağırırsan yeni içerik yeniden girer.

Süreklilik yalnızca yükleme göstergelerinde var: Iskelet ve Donuyor. Dekoratif öğede sonsuz döngü yok.

Yoğunluk üç kademede: KADEME_SAKIN, KADEME_OLCULU (varsayılan) ve KADEME_BELIRGIN. Kademeyi bir kez seç, sonra o kademede kal — tek bir süreyi her geçişe kopyalamak da, her geçişe ayrı süre uydurmak da yanlış.

nar
import "../araclar/arayuz.nar"

fn ciz(yukleniyor: Bool) -> Gorunum {
  if yukleniyor {
    return Kart("Kayıtlar", [
      Iskelet(3),
      Bosluk(8),
      Donuyor("Veri geliyor…")
    ])
  }

  return Kart("Kayıtlar", [
    Sirali([
      Metin("Ayşe"),
      Metin("Mehmet"),
      Metin("Zeynep")
    ]),
    Cizgi,
    Hareketli(NABIZ, Rozet("kaydedildi", TON_BASARI))
  ])
}

fn main() {
  kademeSec(KADEME_SAKIN)
  uygulamaBaslat("#uygulama", true, ciz)
}

Yalnız transform ve opacity hareket eder. width, height, top ve left hareket ettirmek her karede yerleşimi yeniden hesaplatır.

Uyum: .mobil() ile dar ekran

Dar ekrana uyum web'de satırlarca medya sorgusu demektir. Bildirimsel modelde bunun yeri görünümün üstüdür: her görünüm kendi uyarlamasını bir metotla taşır, medya sorgusu yazılmaz.
  • .mobildeSutun() — dar ekranda satır sütuna döner
  • .mobildeGizle() / .masaustundeGizle() — yalnız bir ekranda görünür
  • .mobil(g) — dar ekranda yerine bambaşka bir görünüm gelir

İlk üçü saf CSS sınıfıdır; hiçbir JavaScript çalışmaz. .mobil() ise kırılma noktası değişince uygulamayı yeniden çizer — dar görünüm durum gibi ele alınır. Tek DOM ağacı vardır; iki dalı birden çizip CSS ile birini gizlemek olay dinleyicilerini ve odağı ikiye katlardı.

Metotlar zincirlenir ve üst üste sarmaz: Satir([...]).mobildeSutun().mobildeGizle() tek katmandır. Sınır 600px; telefon dikey ve küçük tabletler dar sayılır. Kendi sorgun gerekiyorsa dildeki medyaEslesir(sorgu) ve medyaDinle(sorgu, islev) yerleşikleri elinde.

nar
import "../araclar/arayuz.nar"

fn ciz(n: Int) -> Gorunum = Kart("Sipariş", [
  // Geniş ekranda yan yana, telefonda alt alta.
  Satir([
    Kutu([Metin("Ürün: Nar")]),
    Kutu([Metin("Adet: 3")]),
    Kutu([Metin("Tutar: 120₺")])
  ]).mobildeSutun(),

  // Geniş ekranda tablo, telefonda liste.
  Tablo(["Ad", "Adet"], [["Nar", "3"]]).mobil(Liste(["Nar × 3"])),

  Rozet("klavye kısayolu: Ctrl+K", TON_BILGI).mobildeGizle(),
  Dugme("Öde", || { }).masaustundeGizle()
])

fn main() {
  uygulamaBaslat("#uygulama", 0, ciz)
}

Sayfa iskeleti: üst şerit, açılış, dip

Buraya kadarki bileşenler sayfanın içini kuruyordu. Bir siteyi ayakta tutan dört parça ise her yerde aynıdır: üstte gezinti şeridi, altında açılış, ortada bölümler, en altta dip.

Bootstrap buna navbar, Foundation top bar, MUI app bar der; hepsi aynı işi yapar. Nar'da dördü de birer görünüm: Gezinti, Kahraman, Bolum, AltBilgi. Hepsi durumsuzdur — hangi bağlantının etkin olduğunu uygulama söyler, bileşen kendi içinde saklamaz.

Aralarına boşluk girmesin diye Yigin kullanılır: Sutun öğeler arasına boşluk koyar, Yigin koymaz.

Dar ekranda üst şerit kendiliğinden menüye iner. Bunun için ayrı bir kod yazmazsın: iki hâl de her zaman çizilir, hangisinin görüneceğine CSS karar verir.

nar
import "araclar/arayuz.nar"

var uygulama: Uygulama<Int>? = none

fn git(i: Int) {
  if uygulama != none {
    uygulama!.degistir(i)
  }
}

fn ciz(sayfa: Int) -> Gorunum = Yigin([
  Gezinti("Kavurma", ["Menü", "Hakkında", "İletişim"], sayfa,
          |i| { git(i) },
          Dugme("Sipariş ver", || { })),

  Kahraman("Günlük kavurma", "Sabah taze kavrulur",
           "Siparişin aynı gün yola çıkar.",
           [Dugme("Menüye bak", || { git(0) }),
            IkincilDugme("Nasıl çalışır?", || { git(1) })]),

  Bolum("Neden biz?", "Üç sebep.",
    Izgara(3, [
      Kart("Taze", [Metin("Kavurma günü kargolanır.")]),
      Kart("Şeffaf", [Metin("Çekirdeğin geldiği yeri yazıyoruz.")]),
      Kart("Esnek", [Metin("Öğütümü sen seçersin.")])
    ])),

  AltBilgi(["Kavurma", "Kurum"], [
    [Bag("Menü", "#"), Bag("Abonelik", "#")],
    [Bag("Hakkında", "#"), Bag("İletişim", "#")]
  ], "© 2026 Kavurma · Mardin")
])

fn main() {
  uygulama = uygulamaBaslat("#uygulama", 0, ciz)
}

Uygulamayı dağıtmak3 konu

Aynı kaynaktan web, masaüstü ve mobil çıktı almak.

Aynı koddan dört hedef

Yazdığın Nar programı tek bir kaynaktır; hangi ortama gideceğini --target seçer.
nar run program.narTerminalde çalıştırır (Node.js)
nar build program.nar --target webTek dosyalık HTML sayfası
nar build program.nar --target masaustuKendi penceresinde açılan uygulama
nar build program.nar --target mobilAndroid ve iOS projesi

Sayfayla konuşan işlemler (bul, olustur) terminalde sessizce boş döner; yani aynı kod her hedefte çöküp kalmaz.

nar
// Aynı program dört hedefte de çalışır.
fn main() {
  let kutu = bul("#uygulama")
  if kutu == none {
    print("terminaldeyiz")
    return
  }
  kutu!.metinYaz("tarayıcıdayız")
}

Masaüstü uygulaması

--target masaustu kendi başına çalışan bir klasör üretir: programın HTML'i, onu kendi penceresinde açan bir başlatıcı ve Windows için bir .cmd dosyası.

Kullanıcının bilgisayarında yalnızca Python olması yeter. pip install pywebview yapılmışsa gerçek bir uygulama penceresi açılır; yoksa tarayıcının adres çubuksuz uygulama penceresi kullanılır.

kabuk
nar build ornekler/yapilacaklar_uygulamasi.nar --target masaustu

# üretilen klasör:
#   index.html    programın kendisi
#   baslat.py     pencereyi açan başlatıcı
#   baslat.cmd    Windows kısayolu
#   BENIOKU.md

Android ve iOS

--target mobil bir Capacitor projesi üretir. Capacitor, uygulamanı gerçek bir mobil uygulamanın içine koyar; mağazaya yüklenebilir.

Üretilen sayfa telefon için hazırlanmıştır: çentikli ekranlarda güvenli alan boşluğu bırakır, dokunma hedefleri parmakla basılacak boyuttadır (44 piksel) ve çift dokunmayla yakınlaştırma kapalıdır.

Önce telefon olmadan dene: üretilen www/index.html dosyası tarayıcıda doğrudan açılır.

Dürüst not: Android paketi için Android Studio, iOS için macOS + Xcode gerekir. Bu paketleyici projeyi hazır eder ama derlemez; üretilen proje bir cihazda denenmemiştir.

kabuk
nar build ornekler/yapilacaklar_uygulamasi.nar --target mobil

cd cikti/yapilacaklar_uygulamasi-mobil
npm install
npx cap add android
npx cap open android     # Android Studio açılır, ▶ ile çalıştır

Nar, Nar ile yazılıyor3 konu

Derleyicinin Nar ile yazılan parçaları.

Dilin kendi kendini derlemesi

Nar'ın ilk derleyicisi Python ile yazıldı — bir dilin başlangıçta başka bir dile ihtiyacı vardır. Ama olgun bir dil kendi derleyicisini kendi diliyle yazabilmelidir; buna self-hosting denir.

Nar bu yolda ilerliyor. Şu ana kadar Nar diliyle yazılanlar:

derleyici/lexer.narSözcüksel çözümleyici — kaynağı token'lara böler
derleyici/ast.narSoyut sözdizim ağacı, konumlar ve S-ifadesi yazıcı
derleyici/parser.narSözdizim çözümleyici — token'lardan ağaç kurar
derleyici/tipler.narTip sistemi — atanabilirlik, ortak tip, tip çıkarımı
derleyici/denetleyici.narTip denetleyici (bildirim aşaması)
derleyici/denetle.narGiriş noktası; içe aktarmaları izler
araclar/renklendirici.narSözdizimi renklendirici (bu sayfadaki renkler)
araclar/bicimlendirici.narKod biçimlendirici (nar fmt)
araclar/arayuz.narBildirimsel arayüz kütüphanesi
araclar/json.narJSON okuyucu ve yazıcı
araclar/ide_uygulamasi.narNar IDE'nin tüm davranışı

Sırada gövde denetimi (ifadeler ve deyimler) ve kod üreteci var.

kabuk
# Nar ile yazılmış derleyiciyle bir dosyayı denetle
nar ozdenetim ornekler/yapilacaklar_uygulamasi.nar

# Derleyici kendi kaynağını da okuyabiliyor
nar ozdenetim derleyici/parser.nar --kutuphane

nar ozdenetim

Nar ile yazılmış derleyiciyi elle denemek için bir komut var. nar ozdenetim önce o derleyiciyi (Nar kaynağından) JavaScript'e çevirir, sonra Node ile çalıştırıp verdiğin dosyayı denetler.

İçe aktarmaları da izler: import edilen dosyaların bildirimleri okunur, böylece başka dosyadaki tipler bilinir.

Kapsam: sözcüksel çözümleme, sözdizim çözümleme ve tip denetiminin bildirim aşaması — tipler, imzalar, arayüz uyumu. Gövde denetimi (ifadeler, deyimler) henüz Nar'a taşınmadı; tam denetim için nar check kullanılır.

kabuk
$ nar ozdenetim derleyici/denetleyici.nar --kutuphane
tamam — hata yok

$ nar ozdenetim bozuk.nar
hata: 2:1 — 'A' tipi zaten tanımlı
hata: 1:15 — bilinmeyen tip: 'Yok'

Doğruluğu nasıl ölçülüyor

Yeni yazılan çözümleyicinin doğru olduğunu nasıl bilirsin? Nar'ın yanıtı: iki çözümleyiciyi aynı kaynakla besleyip sonuçları karşılaştırmak.

Ağaçları karşılaştırabilmek için ortak bir yazım gerekir. Nar bunun için S-ifadesi kullanır: ağacın parantezli metin hâli. Konum bilgisi yazılmaz — iki çözümleyicinin satır/sütun hesabı birebir aynı olmak zorunda değil, ağacın yapısı aynı olmak zorunda.

Karşılaştırma yapay örneklerle sınırlı değil: projedeki bütün .nar dosyaları iki çözümleyiciden de geçirilir. Ayrı bir modda konumlar (satır, sütun) da karşılaştırılır — hata mesajlarının nereyi gösterdiği buna bağlı.

Tip sistemi için aynı yöntem: iki taraf aynı 40 tipi kurar, atanabilirlik, ortak tip ve tip çıkarımı 1600 çift üzerinde karşılaştırılır. Denetleyicide ise hata listeleri — mesaj, satır ve sütunla birlikte.

Bu yöntem gerçek hatalar buldu: Türk alfabesinde olmadığı için harf listesinden düşen q harfi; metin literalindeki ${'{...}'} gömmesinin içeriğinin token'da saklanmaması; desende birden çok _ kullanıldığında üretilen JavaScript'in çökmesi.

nar
// Kaynak
fn kare(x: Int) -> Int = x * x

// İki çözümleyicinin de ürettiği S-ifadesi
(modul [(fn "kare" [] [] [(parametre "x" (tad "Int"))] (tad "Int")
  (blok [(donus (ikili "*" (ad "x") (ad "x")))]))])

Programı düzenlemek4 konu

Dosyalara bölmek, içe aktarmak, biçimlendirmek, test etmek.

Dosyaları bölmek

Program büyüdükçe birden çok dosyaya bölebilirsin. Bir dosyadaki fonksiyonları kullanmak için import yazman yeterli.

Örneğin araclar.nar dosyasında yardımcı fonksiyonlar, program.nar dosyasında ana akış olabilir:

nar
// araclar.nar dosyası:
fn selamla(ad: String) -> String = "Merhaba " + ad


// program.nar dosyası:
import "araclar.nar"

fn main() {
  print(selamla("Nar"))
}

Dosyalar, girdi ve zaman

Terminalde çalışan programlar dosya okuyup yazabilir, kullanıcıdan girdi alabilir ve saate bakabilir.

Not: Bunlar yalnızca nar run ile (Node üzerinde) anlamlıdır. Tarayıcıda dosya okuma none, yazma false döner — program çökmez, sadece iş yapmaz.

Okuma her zaman "olabilir" sonuç verir: dosya yoksa none gelir, kontrol etmen gerekir.

nar
fn main() {
  // Dosya yazma ve okuma
  dosyaYaz("not.txt", "birinci satır\n")
  dosyaEkle("not.txt", "ikinci satır\n")

  let icerik = dosyaOku("not.txt")
  if icerik != none {
    print("satır sayısı:", icerik!.trim().split("\n").len())
  }

  print("var mı:", dosyaVarMi("not.txt"))
  dosyaSil("not.txt")

  // Klasördeki dosyalar
  let dosyalar = klasorListele("ornekler")
  print("kaç dosya:", dosyalar.len())

  // Komut satırı argümanları
  print("argümanlar:", argumanlar())

  // Zaman
  print("şu an:", zamanMetni())
  let basla = simdi()
  var toplam = 0
  for i in 1..=1000 { toplam += i }
  print("geçen süre (ms):", simdi() - basla)

  // Kullanıcıdan girdi (terminalden):
  //   let satir = satirOku()
  //   if satir != none { print("girdin: " + satir!) }
}

Hata mesajlarını okumak

Nar bir hata bulduğunda sana üç şey söyler: nerede olduğu, ne olduğu ve çoğu zaman nasıl düzeltileceği.

Hata mesajını okumak zaman kaybı değil, en hızlı çözüm yoludur. Örneğin değişmez bir değeri değiştirmeye çalışırsan:

nardene
let sayi = 5
sayi = 6
hata mesajı
hata-mesajlari.nar:3:3 — 'sayi' değişmez, değeri değiştirilemez
   |
 3 |   sayi = 6
   |   ^^^^
  ipucu: bildirimde 'let' yerine 'var' kullan

Unuttuğun seçeneği söyler

enum seçeneklerinden birini ele almayı unutursan derleyici tam olarak hangisini unuttuğunu söyler. Bu özellik, sonradan yeni seçenek eklediğinde çok işe yarar.
nardene
enum Durum {
  Acik
  Kapali
  Bekliyor
}

fn main() {
  match Durum.Acik {
    Durum.Acik -> print("açık")
    Durum.Kapali -> print("kapalı")
  }
}
hata mesajı
hata-eksik-dal.nar:8:3 — match tam değil; kapsanmayan varyantlar: Bekliyor
   |
 8 |   match Durum.Acik {
   |   ^^^^^
  ipucu: eksik dalları ekle ya da '_ -> ...' dalı koy

Hızlı başvuru9 tablo

Ararken bakılacak tablolar: hazır fonksiyonlar, metotlar, işleçler.

Hazır fonksiyonlar

Her yerde kullanabileceğin, hiçbir şey yazmadan gelen fonksiyonlar.

print(a, b, ...)Ekrana yazar, aralara boşluk koyar
str(x)Herhangi bir değeri metne çevirir
len(x)Metin, liste ya da sözlüğün uzunluğu
int(x)Metni ya da ondalıklıyı tam sayıya çevirir
float(x)Metni ya da tam sayıyı ondalıklıya çevirir
abs(x)Mutlak değer (işareti atar)
min(a, b) · max(a, b)Küçük olan · büyük olan
sqrt(x)Karekök
pow(a, b)Üs alma: a üzeri b
floor(x) · ceil(x) · round(x)Aşağı · yukarı · en yakına yuvarlar
random()0 ile 1 arasında rastgele ondalıklı sayı
panic(mesaj)Programı verilen mesajla durdurur
assert(kosul, mesaj)Koşul yanlışsa programı durdurur

Metin işlemleri

Bir metnin sonuna nokta koyup çağırılır: "abc".upper()

len()Harf sayısı
upper() · lower()Büyük · küçük harfe çevirir
upperTr() · lowerTr()Türkçe kurallarıyla: i↔İ, I↔ı
trim()Baştaki ve sondaki boşlukları atar
split(ayrac)Ayraca göre parçalayıp liste verir
contains(parca)İçinde geçiyor mu
replace(eski, yeni)Tüm geçtiği yerleri değiştirir
startsWith(x) · endsWith(x)Şununla başlıyor mu · bitiyor mu
slice(bas, son)Belirtilen aralığı keser
charAt(i)i numaralı harf
indexOf(parca)Kaçıncı sırada geçiyor (yoksa -1)
repeat(n)n kez tekrarlar
ters()Ters çevirir
solaDoldur(n, dolgu)Sabit genişliğe getirir: "7" → "007"
sagaDoldur(n, dolgu)Sağdan doldurarak genişletir

Liste işlemleri

Bir listenin sonuna nokta koyup çağırılır: [1,2].len()

len()Eleman sayısı
push(x)Sona ekler
pop()Sondakini alır ve çıkarır (boşsa none)
first() · last()İlk · son eleman (boşsa none)
contains(x)İçinde var mı
indexOf(x)Kaçıncı sırada (yoksa -1)
slice(bas, son)Belirtilen aralığı alır
reverse()Ters çevrilmiş kopyasını verir
sort()Sıralanmış kopyasını verir
join(ayrac)Metin listesini birleştirir
map(f)Her elemanı dönüştürür
filter(f)Koşula uyanları seçer
reduce(f, baslangic)Hepsini tek değere indirger
benzersiz()Tekrar edenleri atar
say(x)x kaç kez geçiyor
sirala(|a, b| a - b)Kendi ölçütünle sıralar
ters()Ters çevrilmiş kopyası
duzlestir()İç içe listeyi bir kat açar
eslestir(b)İki listeyi çiftler: [(a, b), …]

Sayı listesi işlemleri

Fonksiyon yazmadan kullanılır; yalnızca sayı listelerinde geçerlidir.

toplam()Hepsinin toplamı
carpim()Hepsinin çarpımı
ortalama()Ortalaması (her zaman ondalıklı)
enBuyuk() · enKucuk()En büyük · en küçük eleman (boşsa none)
kat(n)Her elemanı n ile çarpar
artir(n)Her elemana n ekler
buyukler(n) · kucukler(n)n'den büyük · küçük olanlar
ciftler() · tekler()Çift · tek sayılar (yalnızca Int)

Metin listesi işlemleri

Yalnızca metin listelerinde geçerlidir.

buyukHarf() · kucukHarf()Hepsini Türkçe kurallarıyla büyütür · küçültür
icerenler(parca)İçinde o parça geçenleri seçer

Sözlük işlemleri

Bir sözlüğün sonuna nokta koyup çağırılır.

len()Kaç kayıt var
get(anahtar)Değeri verir (yoksa none)
set(anahtar, deger)Ekler ya da günceller
has(anahtar)Bu anahtar var mı
remove(anahtar)Kaydı siler
keys() · values()Anahtarların · değerlerin listesi

Sayfa işlemleri

Yalnızca tarayıcıda anlamlıdır; Node'da bul none döner.

bul(secici)Bir öğe seçer (yoksa none)
bulHepsi(secici)Eşleşen tüm öğeler
olustur(etiket)Yeni öğe yapar
govde()Sayfanın gövdesi
zamanla(ms, islev)Belirtilen süre sonra çalıştırır
istek(yontem, url, govde, islev)Ağ isteği; sonuç islev'e gelir
e.metin() · e.metinYaz(s)İçindeki yazıyı okur · değiştirir
e.html() · e.htmlYaz(s)İçeriği HTML olarak okur · yazar
e.deger() · e.degerYaz(s)Giriş kutusunun değeri
e.sinifEkle(s) · sinifSil(s) · sinifVarMi(s)CSS sınıfı işlemleri
e.ozellik(ad) · ozellikYaz(ad, d)Öznitelik okur · yazar
e.stil(ad, deger)Tek bir stil kuralı verir
e.dinle(olay, islev)Olay dinler (click, input…)
e.ekle(cocuk) · e.cikar()Çocuk ekler · kendini kaldırır
e.temizle() · e.odaklan()İçini boşaltır · odağı verir
e.bul(secici) · e.bulHepsi(secici)Kendi içinde arar

Dosya, girdi ve zaman

Yalnızca terminalde (Node) anlamlıdır; tarayıcıda iş yapmazlar.

dosyaOku(yol)Dosyayı okur (yoksa none)
dosyaYaz(yol, icerik)Dosyayı yazar (üzerine yazar)
dosyaEkle(yol, icerik)Dosyanın sonuna ekler
dosyaVarMi(yol) · dosyaSil(yol)Var mı · siler
klasorListele(yol)Klasördeki dosya adları
satirOku()Girdiden bir satır okur (bitince none)
tumGirdi()Girdinin tamamını okur
argumanlar()Komut satırı argümanları
komutCalistir(program, argumanlar)Başka bir programı çalıştırır; .cikis() .cikti() .hata()
cik(kod)Programı verilen çıkış koduyla bitirir
simdi()1970'ten beri geçen milisaniye
zamanMetni()Okunabilir tarih-saat

Operatörler

Yukarıdakiler aşağıdakilerden önce hesaplanır.

a.b · a() · a[i] · a!Erişim, çağrı, dizin, zorla aç
-a · !aEksi işaret, değil
* / %Çarpma, bölme, kalan
+ -Toplama (metin birleştirme), çıkarma
.. ..=Aralık (son hariç · son dahil)
< <= > >=Karşılaştırma
== !=Eşit mi, eşit değil mi
&&Ve
||Veya
??Yoksa şunu kullan