BA Barış Akın~/blog

·14 dk okuma·48 okunma

Cobra: Üç Motorlu, GIL'siz, Native Derlenen Bir Betik Dili

Go ile yazılmış Cobra dilini baştan sona tanıtan bir yazı: end ile kapanan bloklar, struct ve record, decorator'lar, GIL olmadan gerçek paralellik, kanallar, zengin standart kütüphane, LSP ve hata ayıklayıcı. Ağaç yürüten yorumlayıcı, bytecode VM ve PyPy'yi geçen native derleyici nasıl çalışıyor?

Cobra: Üç Motorlu, GIL'siz, Native Derlenen Bir Betik Dili

Cobra, Go ile yazdığım genel amaçlı bir betik dili. Blokları girintiyle değil end ile kapatıyor, tek bir statik dosya olarak dağıtılıyor ve aynı programı üç farklı yoldan çalıştırabiliyor: ağaç yürüten bir yorumlayıcı, bir bytecode VM ve doğrudan makine koduna derleyen bir native derleyici. GIL yok; async görevler gerçekten bütün çekirdeklerde aynı anda koşuyor. Yanında bir LSP sunucusu, bir biçimlendirici, bir VS Code eklentisi, bir test çatısı, Django tarzı bir web çatısı ve NumPy benzeri bir sayısal kütüphane de var.

Bu yazıda dili baştan sona dolaşacağım: sözdizimi, veri tipleri, fonksiyonlar, nesneler, hatalar, eşzamanlılık, standart kütüphane, araçlar ve en sonunda iç yapı ile performans. Yazıdaki bütün örnekleri Cobra 0.13.0 ile her iki motorda da çalıştırdım; gösterdiğim çıktılar gerçek çıktılar. Dilin sitesi cobralang.baltavista.com adresinde.

Neden bir dil daha?

Cobra'yı katman katman inşa ettim: önce lexer, sonra Pratt parser, sonra ağaç yürüten değerlendirici, ardından bytecode derleyici ve yığın makinesi, en son da native derleyici. Baştan beri tek bir kural vardı: her özellik iki motora birden girer, ikisinin de aynı davrandığı test edilir. Testler ve benchmark'lar iki motorun bayt bayt aynı çıktıyı üretmesini şart koşuyor; çıktılar farklıysa sonuç raporlanmıyor bile.

Hedeflediğim yer şuydu:

  • Kurulumsuz betikler: bir dosyayı kopyala, çalıştır. venv yok, paket yöneticisi yok.
  • Gerçek paralellik: CPU'ya yüklenen işi çekirdeklere dağıtınca gerçekten hızlansın.
  • Gömülebilirlik ve okunabilirlik: dilin tamamı, baştan sona okunup anlaşılabilecek kadar küçük kalsın.
  • Hız gerektiğinde: aynı kaynağı native derleyip sayısal işlerde JIT'li çalışma ortamlarının önüne geçebilsin.

Altmış saniyede Cobra

let name = "Cobra"
let langs = ["go", "rill", "cobra"]
let ages = {"ada": 36, "linus": 28}

def greet(who, times)
    let out = []
    for i in range(times)
        out.push("merhaba ${who} #${i + 1}")
    end
    return out
end

for line in greet(name, 2)
    print(line)
end

let squares = map(def(x) -> x * x, [1, 2, 3, 4])
print(squares, squares[-1], squares[1:3])
print(filter(def(n) -> n % 2 == 0, range(10)))
let a, b = 1, 2
a, b = b, a
print(a, b, 7 / 2, float(7) / 2)
for k in ages
    print(k, ages[k])
end
print("  a,b  ".strip().split(","))

Çıktı:

merhaba Cobra #1
merhaba Cobra #2
[1, 4, 9, 16] 16 [4, 9]
[0, 2, 4, 6, 8]
2 1 3 3.5
ada 36
linus 28
["a", "b"]

Kurallar az ve net:

  • Satır sonu ifadeyi bitirir, noktalı virgül yok.
  • Bloklar def, if, elif, else, while, for, struct, try gibi bir anahtar kelimeyle açılır ve end ile kapanır. Girinti tamamen kozmetik; yanlış girinti programı bozmaz.
  • # satır sonuna kadar yorumdur.
  • Mantıksal işleçler kelimedir: and, or, not. Boş değer null.
  • ( [ { içindeki ifadeler ve çağrı argümanları birden fazla satıra yayılabilir.

Değişkenler ve tipler

let mevcut kapsamda yeni bir isim tanımlar, düz x = ... var olan bir ismi günceller. Tanımlanmamış bir isme atama hatadır; yazım hatası yüzünden sessizce yeni bir değişken doğmaz. const değiştirilemez bir bağ tanımlar; ama değişmezlik bağın kendisinedir, değerin değil: const xs = [1] yazınca xs = ... yasaktır, xs.push(2) serbesttir.

Tipler: tamsayı, ondalıklı sayı, metin, boolean, null, liste, sözlük ve birinci sınıf fonksiyonlar. Birkaç ayrıntı:

  • Tamsayı ile ondalıklı karışınca sonuç ondalıklı olur: 1 + 2.5 sonucu 3.5, 1 == 1.0 doğru.
  • İki tamsayı arasında / tamsayı bölmesidir: 7 / 2 sonucu 3. Ondalık sonuç için float(7) / 2.
  • Yanlış sayılan değerler: false, null, 0, "", [], {}.
  • and ve or kısa devre yapar ve karar veren işleneni döndürür: 0 or "x" sonucu "x".
  • Çoklu atama tek satırda takas yapar: a, b = b, a. Sağ taraf tamamen hesaplandıktan sonra bağlama yapılır.
  • += -= *= /= %= değişkenlerde, indekslerde (xs[i] += 1) ve alanlarda (self.n += 1) çalışır.

Koleksiyonlar ve metinler

Listeler referans tipidir; let b = a kopyalamaz, aynı listeyi gösterir. Negatif indeks sondan sayar (l[-1]), + birleştirir, == derin karşılaştırır. Metotlar: push, pop, contains, find, count, reverse, sort.

Dilimleme Python'daki gibi seq[low:high:step] biçiminde: xs[:2], xs[1:], xs[::-1]. Sınır dışı değerler kırpılır. Listelerde yeni bir liste, metinlerde rune tabanlı (UTF-8 güvenli) bir metin, range üzerinde ise yine tembel bir aralık döner.

Sözlükler ekleme sırasını korur. Anahtar metin, tamsayı, boolean ya da null olabilir. Olmayan anahtarı d[k] ile okumak hatadır; has ile kontrol edilir ya da get(k, varsayılan) kullanılır. Metotlar: keys, values, items, has, get, del.

Metinler değiştirilemez ve indekslenebilir. upper, lower, strip, split, join, replace, startswith, find, count gibi metotlar zincirlenebilir. Metin içine ifade gömmek için ${...} kullanılır; içine her ifade yazılabilir, iç içe bile olabilir. Çok satırlı metinler için üç tırnaklı dizgiler de var.

Sıralama bir karşılaştırıcı da alabiliyor; sayıları ekranda düzgün göstermek için de fmt modülü var:

import "fmt"
import "json"
let rows = json.parse("[{\"ad\": \"klavye\", \"fiyat\": 1299}, {\"ad\": \"fare\", \"fiyat\": 349.5}]")
rows.sort(def(a, b) -> a["fiyat"] < b["fiyat"])
for r in rows
    print(fmt.pad(r["ad"], 8, " "), fmt.group(r["fiyat"], 2))
end
    fare 349.50
  klavye 1,299.00

sort(less) kararlı bir sıralama kullanıyor; eşit elemanlar giriş sırasını koruduğu için çok anahtarlı sıralamalar art arda yazılarak kurulabiliyor. fmt.group ve math.round yarımları çifte yuvarlıyor (banker's rounding) ve yuvarlamayı ikili kayan nokta yerine ondalık metin üzerinden yapıyor; 0.145 * 100 gibi sinsi hatalar burada yaşanmıyor.

Fonksiyonlar, closure'lar ve decorator'lar

Fonksiyonlar birinci sınıf değerdir ve tanımlandıkları kapsamı yakalarlar. Tek ifadelik anonim fonksiyonlar ok biçimiyle yazılır: def(x) -> x * 2. Çok satırlı gövde için def(x) ... end blok biçimi var. map, filter, reduce, zip, enumerate, any, all yerleşik.

Decorator'lar bir def'in üstüne yazılır ve @d sözdizimi name = d(name) anlamına gelir. Web rotalarını kaydetmenin doğal yolu bu:

let routes = {}

def route(path)
    return def(fn)
        routes[path] = fn
        return fn
    end
end

@route("/")
def home(req)
    return "ana sayfa"
end

@route("/hello")
def hello(req)
    return "merhaba ${req["name"]}"
end

for path in routes
    print(path, "->", routes[path]({"name": "ada"}))
end
/ -> ana sayfa
/hello -> merhaba ada

Decorator'lar üst üste binebilir; def'e en yakın olan önce uygulanır. async def üzerinde de çalışırlar.

Nesneler: struct, kalıtım, record, contract

Cobra'da sınıflar struct ile tanımlanıyor (class da eş anlamlı olarak kabul ediliyor). init kurucu, self metot gövdelerinde örtük. Tek kalıtım < ile, üst sınıfın metoduna super ile ulaşılır. Bir struct gövdesinde sınıf sabitleri (const), alan gibi okunan özellikler (get), atama yakalayıcıları (set) ve statik metotlar (static def) olabilir.

record ise değiştirilemez ve değer ile karşılaştırılan bir struct: alanları aynı olan iki record == sayılır, with bir alanı değiştirilmiş yeni bir kopya döndürür.

struct Shape
    def init(name)
        self.name = name
    end
    def describe()
        return "${self.name}: alan ${self.area()}"
    end
end

struct Circle < Shape
    const PI = 3.14159
    def init(r)
        super.init("daire")
        self.r = r
    end
    def area()
        return Circle.PI * self.r * self.r
    end
end

struct Rect < Shape
    def init(w, h)
        super.init("dikdörtgen")
        self.w = w
        self.h = h
    end
    def area()
        return self.w * self.h
    end
    get square()
        return self.w == self.h
    end
end

record Point
    def init(x, y)
        self.x = x
        self.y = y
    end
end

for s in [Circle(2), Rect(3, 3)]
    print(s.describe())
end
print(Rect(2, 5).square)
let p = Point(1, 2)
print(p == Point(1, 2), p.with({"x": 9}))
try
    p.x = 5
catch err
    print("hata:", err)
end
daire: alan 12.56636
dikdörtgen: alan 9
false
true Point{x: 9, y: 2}
hata: line 49: cannot modify immutable record Point

Shape.describe içinde çağrılan self.area() alt sınıfta tanımlı; metotlar örneğin gerçek tipine göre dinamik olarak seçiliyor. contract ise hafif bir arayüz: gereken metot adlarını listeler, implements(x, Drawable) bir değerin hepsini tanımlayıp tanımlamadığını söyler.

Hatalar: try, catch, finally

throw her değeri fırlatabilir ve değer olduğu gibi yakalanır. Çalışma zamanı hataları satır numaralı mesaj olarak yakalanır. finally her zaman çalışır; return, break ve continue da try bloklarından olduğu gibi geçer.

def divide(a, b)
    if b == 0
        throw "sıfıra bölme"
    end
    return a / b
end
try
    print(divide(10, 2))
    print(divide(1, 0))
catch e
    print("yakalandı:", e)
finally
    print("finally her zaman çalışır")
end
@memo
def x()
end

Sondaki @memo bilerek tanımsız bırakıldı. İki motorun farkı tam burada görünüyor: ağaç yürüten yorumlayıcı programı o satıra kadar çalıştırıp orada duruyor, VM ise programı derlerken tanımsız ismi yakalıyor ve tek satır bile çalıştırmıyor. Hata mesajı ve satır numarası iki motorda da aynı:

Cobra terminalde: iki motor, paralellik ve native derleme

Derleme anında yakalanan hatalar sadece tanımsız değişkenler değil: yerleşik isimlere atama, döngü dışındaki break ve const bir ismi yeniden atamak da çalıştırmadan önce reddediliyor.

Eşzamanlılık: async, await ve GIL olmadan paralellik

async def ile tanımlanan bir fonksiyon çağrıldığında hemen bir görev döndürür ve gövdesi kendi işletim sistemi iş parçacığında çalışır. await gorev sonucu bekler, await [g1, g2, ...] hepsini toplar ve sonuçları sırayla verir. Görevde atılan hata await noktasında yeniden fırlatılır.

GIL olmadığı için CPU'ya yüklenen iş gerçekten hızlanıyor. Aynı hesabı önce sırayla, sonra dört görevle yapalım:

import "time"
import "fmt"

def work(n)
    let s = 0
    let i = 0
    while i < n
        s = s + i % 7
        i = i + 1
    end
    return s
end

async def job(n)
    return work(n)
end

let t0 = time.clock()
let seq = [work(5000000), work(5000000), work(5000000), work(5000000)]
let t1 = time.clock()
let par = await [job(5000000), job(5000000), job(5000000), job(5000000)]
let t2 = time.clock()
print(seq == par)
print("sıralı ${fmt.decimal(t1 - t0, 3)}s, paralel ${fmt.decimal(t2 - t1, 3)}s")

10 çekirdekli bir Apple M serisi makinede VM ile çıktı sıralı 0.584s, paralel 0.186s oldu: kodda tek bir kilit, havuz ya da ek sözdizimi olmadan yaklaşık 3 kat. pmap(fn, liste) aynı fikrin en kısa hali: map ile aynı çağrı, ama elemanlar çekirdeklere dağıtılıyor ve sonuçlar giriş sırasını koruyor.

Paralellik paylaşılan durumun senkronizasyonunu programcıya bırakıyor. Bunun için yerleşik araçlar var:

  • Mutex() ve RWMutex(): lock, unlock, try_lock, okuyucu tarafında rlock, runlock.
  • Channel() ve Channel(n): Go'daki gibi tamponsuz ya da n elemanlık tamponlu kanallar; send, recv, close, try_send, try_recv.
  • select(ops): birden fazla kanalı aynı anda bekler, hangisi hazırsa onunla devam eder; varsayılan değer verilirse beklemez.
let ch = Channel(4)
let done = Channel()

async def producer()
    for i in range(5)
        ch.send(i * i)
    end
    ch.close()
end

async def consumer()
    let total = 0
    let v = ch.recv()
    while v != null
        total += v
        v = ch.recv()
    end
    done.send(total)
end

producer()
consumer()
print("toplam:", done.recv())
print(pmap(def(x) -> x * 10, [1, 2, 3]))
toplam: 30
[10, 20, 30]

Global kapsam ve modül önbelleği içeride senkronize; farklı global'lere dokunan görevler her zaman güvende. Yarışan bir görevdeki kurtarılabilir bir panik (paylaşılan listede sınır dışı indeks gibi) programı çökertmek yerine yakalanabilir bir "data race in task" hatasına dönüşüyor. Bu bir güvenlik ağı, kilit yerine geçmiyor: Go'da olduğu gibi paylaşılan bir sözlüğü kilitsiz değiştirmek ölümcül hata.

Modüller ve standart kütüphane

import "lib/utils.cobra" dosyayı bir kez çalıştırır, önbelleğe alır ve bir utils modül nesnesi bağlar; as ile başka ad verilebilir. Döngüsel import hatadır, modüller dışarıdan salt okunurdur.

Yerleşik native modüller Go ile yazıldı ve iki motor tarafından ortak kullanılıyor. Hepsindeki hatalar yakalanabilir:

modüliçerik
fsdosya okuma, yazma, ekleme, satır satır okuma, dizin işlemleri
math, randommatematik fonksiyonları, tohumlanabilir rastgele sayılar
fmtsayıları ondalık, binlik ayraçlı ve dolgulu metne çevirme
jsonanahtar sırasını koruyan parse, girintili stringify
timesaat, monoton sayaç, strftime biçimli format ve parse
reRE2 sözdizimiyle doğrusal zamanlı düzenli ifadeler
httpistemci (get, post, request) ve sunucu (serve), SSE akışı
proc, netalt süreçler, TCP soketleri, TLS
crypto, encodingsha256, hmac, PBKDF2 parola özeti, base64, hex
os, runtimeargümanlar, ortam değişkenleri, GC ve bellek istatistikleri
testyerleşik birim test çatısı

Bir HTTP sunucusu, çatı kullanmadan bile birkaç satır:

import "http"
import "json"

def handler(req)
    if req["path"] == "/hello"
        let name = req["query"].get("name", "dünya")
        return {"headers": {"Content-Type": "application/json"}, "body": json.stringify({"merhaba": name})}
    end
    return {"status": 404, "body": "bulunamadı"}
end

http.serve(":8099", handler)

curl "localhost:8099/hello?name=ada" isteği {"merhaba":"ada"} döndürüyor. İstek sözlüğünde yöntem, yol, sorgu, başlıklar, gövde, istemci adresi ve multipart/form-data ile gelen dosyalar hazır geliyor. İşleyiciler istek başına bir goroutine ile paralel çalışıyor.

Testler de dilin içinde:

import "test"

@test.it
def toplama()
    test.assert_eq(2 + 2, 4)
end

@test.it
def derin_esitlik()
    test.assert_eq([1, {"a": [2]}], [1, {"a": [2]}])
end

@test.it
def hata_atar()
    test.assert_raises(def() -> 1 / 0)
end

let summary = test.run()
ok    toplama
ok    derin_esitlik
ok    hata_atar

3 passed, 0 failed, 3 total

Araçlar

Tek ikili dosya, bütün modlar:

cobra app.cobra                  # ağaç yürüten yorumlayıcı
cobra --vm app.cobra             # bytecode VM
cobra --native app.cobra         # makine koduna derle ve çalıştır (önbellekli)
cobra build app.cobra            # app.cobrac bytecode dosyası
cobra build --exe app.cobra      # VM gömülü, tek başına çalışan dosya
cobra build --native app.cobra   # tek başına native çalıştırılabilir dosya
cobra                            # REPL (çok satırlı, durumu korur)
cobra fmt -w app.cobra           # kaynak biçimlendirici
cobra watch app.cobra            # dosya değişince yeniden çalıştır
cobra lsp                        # dil sunucusu
cobra dap                        # hata ayıklayıcı (Debug Adapter Protocol)
  • REPL iki motorda da var; açık bir def ya da if bloğu yazarken .. devam istemiyle girişi tamamlamanızı bekliyor.
  • LSP sunucusu gerçek parser ve derleyiciden canlı teşhis veriyor: yazarken sözdizimi hataları ve tanımsız değişkenler, tamamlama, üzerine gelince açıklama, sembol listesi, tanıma git.
  • VS Code eklentisi sözdizimi renklendirme, bloğa duyarlı girinti, snippet'ler, modül üyelerinde tamamlama ve outline görünümü veriyor; cobra PATH'teyse LSP'yi kendisi başlatıyor.
  • Hata ayıklayıcı: cobra dap editörlere Debug Adapter Protocol üzerinden kesme noktası, adım adım ilerleme, çağrı yığını ve değişken incelemesi sunuyor. Ayrıca --ast ve --tokens parser'ın ve lexer'ın ne gördüğünü yazdırıyor, çalışma zamanı hataları yığın iziyle geliyor.
  • Go eklentileri: import "ad.so" ile Go ile yazılmış bir eklenti çalışma anında yüklenip modül gibi kullanılabiliyor.

Yayın ikilileri statik ve bağımlılıksız; macOS (arm64, amd64), Linux (amd64, arm64) ve Windows için derleniyor.

İç yapı: bir ön yüz, üç motor

Cobra'nın mimarisi: lexer, parser, AST ve üç motor

Lexer kaynağı token'lara, Pratt parser token'ları AST'ye çeviriyor. Bundan sonra yol üçe ayrılıyor, ama değerlerin davranışı (object paketi) ve standart kütüphane (eval paketi) hepsinde ortak. Bu yüzden bir liste metodunun ya da bir json.parse çağrısının iki motorda farklı davranması mümkün değil.

Ağaç yürüten yorumlayıcı AST düğümlerini doğrudan değerlendiriyor. Hazırlık maliyeti yok; REPL ve kısa betikler için ideal.

Bytecode VM benchmark paketini yaklaşık 4 kat hızlı çalıştırıyor. Bunu sağlayan şeyler, etkisi büyükten küçüğe:

  • Ondalıklı sayılar ve struct örnekleri için VM başına ayırma arenaları. GIL olmadığı ve arenalar VM'e özel olduğu için güvenli.
  • Kayıtlara alınmış bir dağıtım döngüsü: komut işaretçisi, yığın işaretçisi ve değer yığını yerel değişkenlerde tutuluyor, sadece çağrılarda ve hata yollarında geri yazılıyor. Bytecode OpHalt ile bittiği için döngüde sınır kontrolü yok.
  • On bir birleşik süper komut: while i < n gibi karşılaştır ve atla, i = i + 1 gibi hesapla ve yaz, yerel.alan okuma, liste[yerel] indeksleme gibi en sık kalıplar tek dağıtımda.
  • Tamsayı ve ondalıklı aritmetik için satır içi hızlı yollar ve küçük tamsayıların önbelleklenmesi (-1024 ile 131071 arası).
  • Önceden ayrılmış bir dizide yaşayan çağrı çerçeveleri: çağrı başına sıfır ayırma.
  • Metin anahtarlı sözlükler için açık adreslemeli özel bir hash tablosu, metin nesnelerinde önbelleklenmiş hash değerleri.
  • Satır tablosu aramaları sıcak yoldan tamamen çıkarıldı; hata mesajları satır numarasını ancak gerektiğinde hesaplıyor.

Native derleyici tüm program üzerinde tip çıkarımı yapıyor. Cobra dinamik tipli, ama gerçek programlar çoğunlukla tip olarak kararlı: bir değişken hep tamsayı, bir liste hep ondalıklı tutuyor. Derleyici bunu ispatlayabildiği yerde tamsayıları ve ondalıklıları kutusuz, struct'ları düz, listeleri ve sözlükleri native dilim ve map olarak üretiyor; kod üretici olarak Go araç zincirini kullanıyor. Çıktı, ondalık biçimlendirme ve yuvarlamaya kadar VM'inkiyle bayt bayt aynı olacak şekilde doğrulanıyor.

Native derlemenin kapsamı dilin tip olarak kararlı çekirdeği: fonksiyonlar, init ve metotları olan struct'lar, listeler, sözlükler, metinler, döngüler ve math. Closure, async, try/catch, kalıtım, record ya da başka modüller kullanan bir program "native derlenemez" diye raporlanıyor ve neyin engel olduğu tek tek yazılıyor; o program VM'de değişmeden çalışıyor. Native dosya üretmek için derleme makinesinde Go gerekiyor, üretilen dosyayı çalıştırmak için hiçbir şey gerekmiyor (benim makinemde yaklaşık 2 MB'lık statik bir Mach-O dosyası).

Performans

Native derleme ve iki motorun karşılaştırması

Üstteki grafik dilin sitesinde yayınlanan ölçümler: n-body, binary-trees, spectral-norm, mandelbrot ve kelime frekansı testleri Cobra'da ve Python'da birebir aynı biçimde yazılmış, bütün çalışma ortamları aynı çıktıyı basıyor. Cobra native toplamda 0.28 saniyeyle PyPy'nin izleyen JIT'inin (0.44 saniye) önünde; CPython 3.14 aynı paketi 5.99 saniyede bitiriyor.

Alttaki grafik altı mikro testte iki motoru karşılaştırıyor: özyinelemeli çağrılarda (fib) VM 10 kattan fazla, toplamda 4.1 kat hızlı.

Dürüst olmak gerekirse VM her işte CPython'u geçmiyor. Bu yazı için 600×600 piksellik, 200 iterasyonluk bir mandelbrot döngüsünü kendim ölçtüm: ağaç yürüten yorumlayıcı 16.5 saniye, VM 2.5 saniye, CPython 3.14 1.7 saniye, native derlenmiş Cobra ise 0.07 saniye (derleme bir kere yapılıp önbelleğe alındıktan sonra). Yoğun ondalıklı aritmetik ve karmaşık döngü koşullarında VM'in kutulama maliyeti hissediliyor; bu tür işler için doğru araç zaten --native. Cobra'nın asıl alanı hızlı ve kurulumsuz betikler, gömülü betik dili kullanımı ve baştan sona anlaşılabilecek kadar küçük bir çalışma ortamı.

Ekosistem: cobranum ve Venom

cobranum Gonum üzerine, macOS'ta cgo ile derlendiğinde Apple Accelerate (BLAS/LAPACK) üzerine kurulmuş, NumPy tarzı bir sayısal dizi kütüphanesi. Bir ve iki boyutlu yoğun diziler, vektörel işlemler, broadcasting, indirgemeler ve lineer cebir sunuyor. Kitapta en küçük kareler, PCA, PageRank, sayısal integral ve diferansiyel denklemler gibi bilimsel örneklerle anlatılıyor:

cobranum ile Lotka-Volterra av ve avcı modeli

Venom tamamen Cobra ile yazılmış, Django'dan ilham alan bir web çatısı: decorator ile rotalama, OxiDB üzerinde bir ORM, kalıtımlı şablon motoru, middleware, oturumlar, statik dosyalar ve CSRF koruması. Cobra'da sınıf kalıtımı yerine fabrika fonksiyonları tercih edildiği için model bir fabrikadan üretiliyor:

import "venom/router.cobra"
import "venom/response.cobra"
import "venom/orm.cobra"

let route = router.route
let db    = orm.connect("127.0.0.1", 4444)
let User  = orm.model(db, "users").ensure()

@route("GET", "/users/<name>")
def show_user(req)
    let user = User.get({"name": req["params"]["name"]})
    if user == null
        return response.not_found("kullanıcı yok")
    end
    return response.ok(user)
end

router.serve(":8000")

OxiDB'ye bağlantı, Go ile yazılmış oxidb.so eklentisi üzerinden. Aynı ailede OxiDB'nin SQL motoru için Entity Framework tarzı bir ORM (oxisqlorm) de var.

Kitap

Dilin tamamı, Türkçe ve İngilizce iki kitapta anlatılıyor: temellerden kontrol akışına, koleksiyonlardan nesne yönelimine, eşzamanlılıktan iç yapıya ve cobranum'a kadar. İngilizce baskıda bunlara Venom bölümü de ekleniyor. İkisi de PDF olarak ücretsiz:

Denemek için

İkili dosyalar, VS Code eklentisi, cobranum ve kitaplar cobralang.baltavista.com adresinde. macOS'ta (Apple Silicon) en kısa yol:

curl -LO https://cobralang.baltavista.com/releases/cobra-0.13.0-darwin-arm64
chmod +x cobra-0.13.0-darwin-arm64 && mv cobra-0.13.0-darwin-arm64 /usr/local/bin/cobra
cobra --version

Sonra bir dosya açıp print("merhaba") yazmak yeterli.

BA
Barış Akın

25 yılı aşkın deneyime sahip yazılım çözümleri mimarı. FinTech ve dağıtık sistemler; OxiDB, Rill ve Cobra'nın geliştiricisi.