Prototyp ciekawego i minimalistycznego języka programowania

2

Cześć,
Tym razem temat nie dotyczy AI. Chciałem się podzielić takim małym odkryciem. Myślę, że da się skonstruować bardzo silny język w którym byłyby tylko definicje typów, deklaracje zmiennych, instrukcje przypisania i specjalna instrukcja “execute”. Nie jestem pewien, czy byłby lepszy od np. C++ (który bardzo lubię), ale na pewno byłby bardziej minimalistyczny. Ten pomysł spodobał mi się tak bardzo, że aż zacząłem implementować interpreter tego języka. To tylko eksperyment, ale może ktoś zechce o tym podyskutować.

Przykład1 – brak funkcji.
Istnieje specjalny wbudowany typ “code”. Literały tego typu mają postać ciągu poleceń zamkniętych w nawiasach klamrowych. Zmienne mogą być typu “code”.

variable x:code;	# to jest deklaracja zmiennej x typu code
let variable x = value { execute print “hello world\n”; }
execute variable x;

Przykład2 – brak pętli.
Zamiast iterować po typie wyliczeniowym można zadeklarować typ a potem rodzinę zmiennych:

type person={Gotrek, Gerrudir, Gwaigilion};
let variable introduce_(X:person) = value { execute print “hello, I am “ (X:person) };
execute variable introduce_(X:person);	# tutaj wykonujemy implicite pętlę po typie person

Przykład3 – brak wyrażeń i instrukcji warunkowej.
Zamiast pisać if (wyrażenie) { blok; } postępujemy inaczej – definiujemy rodzinę zmiennych typu code, następnie przypisujemy dwóm różnym zmiennym z tej rodziny kod, który ma być wykonany w alternatywnych przypadkach:

type boolean={false, true};
variable x:boolean;
variable if_x_print_message (V:boolean): code;
let variable if_x_print_message true = value { execute print “x is true\n”; };
let variable if_x_print_message false = value { execute print “x is false\n”; };
...
let x=value true;
...
execute variable if_x_print_message <x>;	# operator <> zwraca wartość zmiennej

Przykład4 – polimorfizm bez klas i dziedziczenia.

type person={Gotrek, Gerrudir, Gwaigilion};
let variable introduce_(X:person) = value { execute print “hello, I am “ (X:person) };
let variable introduce_Gotrek = value { execute print “hi there, me I am Gotrek”; };

execute variable introduce_(X:person);	# tutaj wykonujemy implicite pętlę po typie person
						# ale Gotrek wykona inny kod

Przykład5 – brak parametrów funkcji.
Zamiast parametrów funkcji przypisujemy rodzinie zmiennych pewien kod który zależy od tzw. placeholderów (w tym wypadku X i Y):

type person={Gotrek, Gerrudir, Gwaigilion};
variable  check_whether_(X:person)_likes_(Y:person): code;
let variable check_whether_(X:person)_likes_(Y:person) = 
value { 
execute print (X:person) “ likes “ (Y:person) “:”;
execute if_x_print_message <(X:person)_likes_(Y:person)> ;
};

execute variable check_whether_(X:person)_likes_(Y:person);

Przykład6 – brak stosu i zwracania wartości przez funkcje.
Zamiast funkcji zwracającej wartość mamy kod, który przypisuje wartość zmiennej w bloku, który go wywołuje.

variable my_result: boolean;
variable x:code;
let variable x = value { execute print “we return true”; let variable my_result = value true; };
execute variable x;

Innymi słowy mielibyśmy język w którym nie istniałyby struktury danych, pętle, instrukcje warunkowe, przekazywanie parametrów, klasy, dziedziczenie, alokowanie dynamicznej pamięci itd. A mimo to byłby być może równie silny jak zwykłe języki. Co o tym myślicie?

0

Minimalizm - przez przerzucenie złożoności w inne miejsce klamerki ... no super

Znałem kiedyś "minimalistyczny" język. Nazywał się TCL. Miał jedną konstrukcję (set) i zasadę rozwijania makr. if, while (czy jak tak się zwało) - to wszystko makra. Koszmar.

Znam (jako kibic, słabo) minimalistyczny język FORTH. ten ocenia jako uczciwiem minimalistyczny, z niewielkiej ilosci konstruktów dla maszyny stosowej buduje się bardziej złożone

5

Ciekawy temat, nie powiem :-)

Przykład1 – brak funkcji.

Tak na moje, w swoim kodzie podałeś właśnie przykład funkcji - nie użyłeś słowa kluczowego fn, fun, funcion czy procedure, ale czym w gruncie rzeczy różni się Twoje value { ... } od typowej funkcji anonimowej albo domknięcia?

Przykład2 – brak pętli.

To akurat języki funkcyjne - takie jak Haskell - odkryły dawno temu, zastępując pętlę m.in. rekurencją.

Przykład3 – brak wyrażeń i instrukcji warunkowej.

Skoro w tym języku nie istnieje instrukcja warunkowa, to czym jest let variable if_x_print_message true = value? 🤔 Uruchamia kod warunkowo, więc wygląda na całkiem przyzwoitą instrukcję warunkową!

Przykład4 – polimorfizm bez klas i dziedziczenia.

Ponownie, zachęcam do rzucenia okiem na języki funkcyjne oraz popatrzenia na języki obsługujące pattern matching :-)

Z czegoś blisko -- Rust czy Go również oferują polimorfizm bez klas oraz dziedziczenia (gdzie Rust robi nawet krok na przód i pozwala programiście na etapie kompilacji określić czy funkcja powinna działać w oparciu o vtable (dyn Trait) czy raczej zostać zmonomorfizowana (impl Trait) - bywa przydatne m.in. do lokalnej optymalizacji kodu).

Przykład5 – brak parametrów funkcji.

Brzmi ponownie jak przykład #1: może i nie użyłeś "typowej" składni do definiowania parametrów, ale w praktyce X oraz Y wyglądają mi właśnie na parametry (a jeśli coś chodzi jak kaczka i gdacze jak kaczka... 🙃).

Przykład6 – brak stosu i zwracania wartości przez funkcje. [...] Innymi słowy mielibyśmy język w którym nie istniałyby [...] alokowanie dynamicznej pamięci itd. A mimo to byłby być może równie silny jak zwykłe języki.

Wydaje mi się, że bez zastosowania stosu (albo podobnego wynalazku) Twój język nie może być Turing-complete, a zatem siłą rzeczy nie może konkurować z typowymi językami do zastosowań ogólnych.

Co o tym myślicie?

Powiedziałbym, że pomysł jakiś w tym jest, ale póki co nie czuję się kupiony - wszystko brzmi na rebranding istniejących już konceptów, "tylko że jeśli nie ma słówka function, no to to nie jest funkcja, przecież!".

2

Istnieje specjalny wbudowany typ “code”. Literały tego typu mają postać ciągu poleceń zamkniętych w nawiasach klamrowych. Zmienne mogą być typu “code”.

Czyli twój typ jest niczym innym jak właśnie funkcją (albo bardziej "procedurą")

Zamiast iterować po typie wyliczeniowym można zadeklarować typ a potem rodzinę zmiennych:

Czyli masz po prostu jakieś .each() czy foreach, to dość naturalne dla wielu języków

Nie do końca rozumiem twój zapis w przypadku 3, niemniej to co zrobiłeś to nic innego jak właśnie instrukcja warunkowa. Wiele języków ma zamiast ifów takie rzeczy jak patter matching i przypuszczam ze właśnie coś takiego chciałeś tu pokazać.

Przykład4 – polimorfizm bez klas i dziedziczenia.

To znów wygląda jak pattern matching, albo coś w stylu erlangowych funkcji:

fact(N) when N>0 ->
    N * fact(N-1);
fact(0) ->
    1.

Zamiast parametrów funkcji przypisujemy rodzinie zmiennych pewien kod który zależy od tzw. placeholderów (w tym wypadku X i Y):

A czym niby są te placeholdery jak nie parametrami funkcji właśnie?

Zamiast funkcji zwracającej wartość mamy kod, który przypisuje wartość zmiennej w bloku, który go wywołuje.

Brzmi tragicznie, bo to poleganie na side-effectach i jest bardzo słabo "widoczne". Co więcej generuje kupę problemów związanych ze zmiennymi "globalnymi" i domknięciami.

A mimo to byłby być może równie silny jak zwykłe języki. Co o tym myślicie?

Obawiam się ze jednak nie za bardzo :(

0

@Patryk27:

  1. brak funkcji - miałem na myśli fakt, że w moim języku nie ma specjalnej konstrukcji na funkcje/procedury, a jest jedynie wbudowany typ code. Typ, którego literały mają postać { lista_rozkazów }.
  2. nie dość jasno się wyraziłem - rekurencji też u mnie nie ma, na przykład taki pseudokod:
for (auto x: person)
{
  for (auto y: person)
  {
    for (auto z: place)
    {
      print wartosc_jakies_funkcji_dla(x,y,z);
    }
  }
}

W moim języku pisałoby się:

execute variable wypisz_wartosc_jakiejs_funkcji_dla_(X:person)_(Y:person)_(Z:place);

Pętle by były implicite - dla każdego elementu iloczynu kartezjańskiego person x person x place.

  1. brak instrukcji warunkowej
let variable if_x_print_message true = value { ...};

to jest zwykłe przypisanie, mamy dwuelementową rodzinę zmiennych if_x_print_message (X:boolean), w której jedna zmienna ma nazwę if_x_print_message false, a druga if_x_print_message true. if_x_print_message to tylko identyfikator.

  1. polimorfizm w moim języku jest jeszcze oszczędniejszy, taką mam przynajmniej nadzieję. Mamy rodzinę zmiennych typu code i każda z nich może mieć inną wartość.
  2. brak parametrów funkcji - placeholdery są elementami nazwy funkcji, to tak jakbyśmy zamiast int add(int x, int y) mieli funkcje add_0_0(), add_0_1(), add_1_0() itd. Oczywiście nie będą rozwijane dla wszystkich możliwych wartości, będzie musiało być "lazy" (tak jak wspomniany przez Ciebie Haskell), ale to tylko informacja jak tworzyć nazwy zmiennych z danej rodziny.
  3. brak stosu - mam nadzieję uzyskać równie silny język korzystając z czegoś analogicznego do stosu, a mianowicie rodziny zmiennych.

To prawda, że mógłbym użyć "function" zamiast "code", czy jakiegokolwiek innego określenia na ten wbudowany typ. Chodzi mi o to, że w klasycznych językach kod jest traktowany specjalnie, a w moim byłby jednym więcej typem, tyle, że predefiniowanym.

Nie obrażę się, jeśli się okaże, że ktoś już na to wpadł. Interesuje mnie tylko, czy taki język miałby szansę być równie silny jak np. C++. Nie wiem jaka jest odpowiedź, i bez eksperymentalnej implementacji chyba się nie dowiem.

1

Chodzi mi o to, że w klasycznych językach kod jest traktowany specjalnie, a w moim byłby jednym więcej typem, tyle, że predefiniowanym.

Tylko że to nie prawda. Na poziomie AST przecież funkcja w praktycznie każdym języku imperatywnym jest niczym innym jak listą instrukcji

0

na przykład taki pseudokod: [...]

Z ciekawości, w jaki sposób przedstawiłbyś coś takiego?

for a in 0..10 {
  let x = foo(a);
  
  for b in a..10 {
    let y = bar(b);

    if b % 2 == 0 {
      continue
    }
    
    for c in b..10 {
      zar(x, y, c);
    }
  }
}

mam nadzieję uzyskać równie silny język korzystając z czegoś analogicznego do stosu, a mianowicie rodziny zmiennych.

Co rozumiesz przez pojęcie rodzina zmiennych?

brak parametrów funkcji - placeholdery są elementami nazwy funkcji, to tak jakbyśmy zamiast int add(int x, int y) mieli funkcje add_0_0(), add_0_1(),

Różnica wygląda jedynie na semantyczną, ponieważ w praktyce coś takiego będzie funkcjonowało - o ile dobrze rozumiem - zupełnie tak samo jak funkcja z parametrami.

To trochę jak gdybyś powiedział, że wymyślasz język polski 2.0 w którym nie będzie słów, ale będą zbitki liter oddzielone białymi znakami; i broń może zażółć gęślą jaźń nie jest trzema słowami/wyrazami, a właśnie zbiorem zbitek liter, tak samo jak gdybyśmy mieli z a ż ó ł ć g ę ś l ą j a ź ń.

0

@Shalom:

  1. Tak, tak właśnie jest, mój wbudowany typ pozwala na implementację rodzin zmiennych zawierających kod. Mam jeden typ więcej i parser potrafi wczytać literały tego typu. Nie ma funkcji jako takich,

  2. for_each - wydaje mi się, że moje "pętle" byłyby jednak silniejsze niż for_each, bo podaję tylko informację o typie, a komputer sam to rozwija. Nie muszę pisać for_each, execute wykonuje kod dla wszystkich możliwych zmiennych z rodziny.

  3. instrukcja warunkowa - w moim przykładzie jest rodzina dwóch zmiennych typu code, z których jedna zawiera wartość typu code wykonywanego dla true a druga dla false.

  4. to przypisywanie zmiennych globalnych - wiem, że to brzmi tragicznie. Muszę to przemyśleć. Chciałbym bardzo uwolnić się od stosu.

0

@Shalom:
Kod jest traktowany specjalnie w tym sensie, że istnieje specjalna składnia do funkcji, parametry, stos itp. W moim podejściu to jeden więcej typ, którego wartości w dodatku są umieszczane w zmiennej. Można przypisać zmiennej jakiś kod, wykonać go, a potem przypisać inny kod do tej samej zmiennej i znowu go wykonać. Zmienne typu code są zwykłymi zmiennymi, tyle, że można je wykonywać.

2
Paweł Biernacki napisał(a):

@Shalom:
Kod jest traktowany specjalnie w tym sensie, że istnieje specjalna składnia do funkcji, parametry, stos itp. W moim podejściu to jeden więcej typ, którego wartości w dodatku są umieszczane w zmiennej. Można przypisać zmiennej jakiś kod, wykonać go, a potem przypisać inny kod do tej samej zmiennej i znowu go wykonać. Zmienne typu code są zwykłymi zmiennymi, tyle, że można je wykonywać.

A czym to sie efektywnie rozni od lambdy albo wskaznika na funkcje?

3
Paweł Biernacki napisał(a):

Co o tym myślicie?

Myślę że odkryłeś Scheme (lub może nawet wczesnego Lispa) tylko z inaczej postawionymi klamerkami
BTW Pierwowzór Lispa też miał inaczej stawiane klamerki

ZrobieDobrze napisał(a):

Znałem kiedyś "minimalistyczny" język. Nazywał się TCL. Miał jedną konstrukcję (set) i zasadę rozwijania makr. if, while (czy jak tak się zwało) - to wszystko makra. Koszmar.

TCL nie był taki zły, był ciekawym przypadkiem języka zaprojektowanego jako String-typed (wszystko było Stringiem). Szkoda że umarł :(

0

@Patryk27:

Myślę, że coś takiego:

variable result_of_foo (a:0..10):integer;
variable result_of_bar_(a:0..10)_(b:integer):integer;

variable foo (a:0..10):code;
let variable foo (a:0..10) = value {... };

variable bar (b:integer):code;
let variable bar (b:integer) = value { ...};

variable zar (X:integer) (Y:integer) (Z:integer):code;
let variable zar (X:integer) (Y:integer) (Z:integer)=value {};

variable (a:0..10)_my_example:code;
let variable (a:0..10)_my_example = value
{
    variable x:integer;
    variable inner_code (b:<a>..10):code;
    
    let variable inner_code (a:0..10) (b:<a>..10)= value
    {
        execute variable foo (a:0..10);
        let variable x = variable result_of_foo (a:0..10);
        variable inner_code2 (a:0..10) (b:<a>..10): code;
        
        let variable inner_code2 (a:0..10) (b:<a>..10) = value
        {
            execute variable bar (b:<a>..10);
            let variable y = variable result_of_bar (b:<a>..10);                                    
            
            variable inner_code3 (a:0..10) (b:<a>..10) (b_modulo_2:0..1) : code;
            let variable inner_code3 (a:0..10) (b:<a>..10) 0 = value {};
            let variable inner_code3 (a:0..10) (b:<a>..10) 1 = value
            {
                execute variable zar <x> <y> (c:<b>..10);
            };                                    
            
            variable b_modulo_2:0..1;
            variable calculate_b_modulo_2 (b:<a>..10):code;
            let variable calculate_b_modulo_2 (b:<a>..10) = value { ... };
            execute variable calculate_b_modulo_2 (b:<a>..10);
            execute variable inner_code3 (a:0..10) (b:<a>..10) <b_modulo_2>;            
        };        
        execute variable inner_code2 (a:0..10) 
    };    
    execute variable inner_code (a:0..10) (b:<a>..10);
};

execute variable (a:0..10)_my_example;

Rodzina zmiennych to zbiór zmiennych, których nazwy są generowane w/g jakiegoś schematu, w przykładzie powyżej mamy

variable inner_code3 (a:0..10) (b:<a>..10) (b_modulo_2:0..1) : code;

To jest rodzina zmiennych, np. jedną z nich jest zmienna o nazwie inner_code3 0 0 0.

1
let variable introduce_(X:person) = value { execute print “hello, I am “ (X:person) };
let variable introduce_Gotrek = value { execute print “hi there, me I am Gotrek”; };

To wygląda jak pomysły z Haskella (funkcje deklarowane przez pattern matching: http://learnyouahaskell.com/syntax-in-functions#:~:text=Pattern%20matching%20consists%20of%20specifying,function%20bodies%20for%20different%20patterns. ) tylko ubrane w wydziwioną składnię.

Zamiast parametrów funkcji przypisujemy rodzinie zmiennych pewien kod który zależy od tzw. placeholderów (w tym wypadku X i Y):
...
variable check_whether_(X:person)likes(Y:person): code;

Tego typu pomysły kojarzą mi się z ObjectiveC. I nie jest to dobre skojarzenie.

type person={Gotrek, Gerrudir, Gwaigilion};
let variable introduce_(X:person) = value { execute print “hello, I am “ (X:person) };
execute variable introduce_(X:person);	# tutaj wykonujemy implicite pętlę po typie person

No dobra, ale czym to jest lepsze od np. tego kodu JS:

const persons = ["Gotrek", "Gerrudir", "Gwaigilion"];
const introduce = (person) => { console.log(`hello, I am ${person}`); }
persons.forEach(introduce);

/* output:
hello, I am Gotrek
hello, I am Gerrudir
hello, I am Gwaigilion
*/

?

Co o tym myślicie?

Myślę, że wymyślanie języków jest fajną sprawą i może w przyszłości coś ciekawego wymyślisz z tym. Jakbyś nie odkrywał wszystkiego od zera, tylko inspirował się dorobkiem tego, co zostało już zrobione w niektórych innych językach, to byłoby to bardziej przystępne. Czasami trzeba się cofnąć (do tego co już zostało wymyślone), żeby odkryć coś faktycznie wartościowego. Chociaż z drugiej strony jeśli pójdziesz w dziwne pomysły i będziesz w nie brnąć, to też możesz do czegoś ciekawego dojść, chociaż niekoniecznie do tego, do czego myślisz, że już doszedłeś.
(sam kombinuję sobie z językami, choć jeszcze nie zrobiłem własnego języka, takiego jaki chcę zrobić)

1

@Paweł Biernacki: Wytłumacz, czemu tak walczysz z tym stosem, wywalasz sporo patternów, np., DFS w kilku linijkach....

0

@stivens:
Efektywnie w "normalnych" językach jest stos, przekazywanie parametrów do funkcji itp. Myślę, że rozwiązanie tradycyjne jest gorsze o tyle, że funkcje są stałymi a w moim języku zmienne typu code są zmiennymi właśnie. Nie potrzebuję oddzielnego pojęcia w języku, kod to taki sam typ jak integer, tylko ma inne literały.

0

@lion137:
Walczę ze stosem, ponieważ chciałbym mieć język bezpieczny i minimalistyczny. Dzięki rodzinom zmiennych, również zmiennych typu code, będę w stanie osiągnąć ten sam efekt co np. C++. Bez żadnych struktur danych, klas, dziedziczenia itp.

0

@LukeJL:

Twój kod w JS jawnie wywołuje for_each, gdybyś miał dwie pętle trzeba by tą drugą skryć w lambdzie introduce. To nieelegancke. A ja będę mógł zapisać np.:

variable introduce (X:person) to (Y:person):code;

execute variable introduce (X:person) to (Y:person);

Mogę dla dowolnej pary (X,Y) zredefiniować kod jaki ma być wykonywany kiedy np. Gotrek przedstawia się Gerrudirowi. Pętle są u mnie niejawne.

1
Paweł Biernacki napisał(a):

@stivens:
Efektywnie w "normalnych" językach jest stos, przekazywanie parametrów do funkcji itp.

Też przecież przekazujesz:

 execute variable if_x_print_message <x>;	# operator <> zwraca wartość zmiennej

Tutaj x jest jak rozumiem argumentem funkcji.

Mogę dla dowolnej pary (X,Y) zredefiniować kod jaki ma być wykonywany kiedy np. Gotrek przedstawia się Gerrudirowi. Pętle są u mnie niejawne.

w podobny sposób robiłem eventy do wykrywania kolizji w grze w JS np. miałem metody typu:

collision_player_enemy
collision_player_coin
i później dodawałem stringi, żeby uzyskać nazwę właściwej metody w handlerze zdarzenia:

const methodName = event.type + "_" + objectA.type + "_" + objectB.type;

itp. Ale znowu, była to proteza i w języku programowania, który byłby tworzony od zera, liczyłbym na coś lepszego. Bardziej coś w stylu języków, które mają dobry pattern matching.

Chociaż składanie stringów w ten sposób jest wygodne, ale jako szczegół implementacyjny. Jeśli mam hashmapę(albo coś, co się podobnie zachowuje), to mogę dodać parę stringów i magicznie zakodować pewne informacje. Ale jednak na wyższym poziomie abstrakcji bym to ukrył przez użytkownikiem.

0

@LukeJL:
Operator <> pobiera wartość zmiennej, załóżmy że jest "true", wówczas interpreter poszuka zmiennej if_x_print_message true, która ma wartość typu code i ją wykona.

Jest możliwe odpalenie czegoś takiego:

execute variable wypisz_zmienne <wiek_(X:person)> <imie_(X:person)>;

Interpreter wykona pętlę po wszystkich elementach typu person, dla każdego z nich weźmie wartość zmiennej wiek_(X:person) i wartość zmiennej imie_(X:person) i obie te wartości dołączy do wypisz_zmienne tworząc nową nazwę. Następnie sprawdzi, czy zmienna o tej nazwie istnieje, czy ma typ code i jaką ma wartość. W końcu ta zmienna będzie wykonana.

1
Paweł Biernacki napisał(a):

@LukeJL:

Twój kod w JS jawnie wywołuje for_each, gdybyś miał dwie pętle trzeba by tą drugą skryć w lambdzie introduce. To nieelegancke. A ja będę mógł zapisać np.:

variable introduce (X:person) to (Y:person):code;

execute variable introduce (X:person) to (Y:person);

Mogę dla dowolnej pary (X,Y) zredefiniować kod jaki ma być wykonywany kiedy np. Gotrek przedstawia się Gerrudirowi. Pętle są u mnie niejawne.

Ze tak?

for {
  person1 <- persons
  person2 <- persons
  if person1 != person2
} println(s"Hello $person2! I'm $person1.")

EDIT: a bardziej idac za przykladem to

val introduce = (person1: String, person2: String) => println(s"Hello $person2! I'm $person1.")

for {
  person1 <- persons
  person2 <- persons
  if person1 != person2
} introduce(person1, person2)
0

Interpreter wykona pętlę po wszystkich elementach typu person, dla każdego z nich weźmie wartość zmiennej wiek_(X:person) i wartość zmiennej imie_(X:person) i obie te wartości dołączy do wypisz_zmienne tworząc nową nazwę

Czyli coś jak SQL?

0

@LukeJL:
Trochę jak SQL, ale to nie jest język zapytań. Najważniejsze to, że pozbędę się całego balastu, który ma np. C++. Wszystko będzie załatwiane przez przypisywanie zmiennym wartości i wykonywanie kodu. Pętle niejawnie, instrukcje warunkowe w formie polimorfizmu, kod w zmiennych itp.

0

variable introduce (X:person) to (Y:person):code;
execute variable introduce (X:person) to (Y:person);
Mogę dla dowolnej pary (X,Y) zredefiniować kod jaki ma być wykonywany kiedy np. Gotrek przedstawia się Gerrudirowi. Pętle są u mnie niejawne.

Tak samo da się w Rust:

let pairs = [["Gotrek", "Gerrudir"], ["Gotrek", "Jarek"], 
   ["Alice", "Bob"], ["Charlie", "Snoopy"], ["Mickey", "Donald"]];
    
pairs.iter().for_each(|pair| {
    println!("{}", match pair {
      ["Gotrek", "Gerrudir"] => format!("Jestem Gotrekiem, unga bunga! Cześć Gerrudirze!"),
      ["Gotrek", _] => format!("Jestem Gotrekiem, cześć {}", pair[1]),
      _ => format!("Jestem {}, witaj {}!", pair[0], pair[1])
    });        
});

output:

Jestem Gotrekiem, unga bunga! Cześć Gerrudirze!
Jestem Gotrekiem, cześć Jarek
Jestem Alice, witaj Bob!
Jestem Charlie, witaj Snoopy!
Jestem Mickey, witaj Donald!

masz więc kolekcję i niejawną pętlę po niej*. Masz oddzielny kod dla Gotreka witającego Gerrudira i dla Gotreka witającego kogoś innego, i dla reszty...

* nawiasem mówiąc niejawna pętla tylko po to, żeby była niejawna jest trochę bez sensu... ale cóż, da się.

2

Myślę że bez przykładu przetwarzania obrazu (np. wyostrzenie), dźwięku (np. filtr dolnoprzepustowy) i tekstu (np. 10 najczęstrzych słów w tekście) ten język będzie kolejnym językiem ezoterycznym dającym satysfakcję głównie jego autorowi.

2

ten język będzie kolejnym językiem ezoterycznym

To mi przeszkadza właśnie. Że zamiast budować na znajomej składni i koncepcjach i dodawać swoje unikalne propozycje (jak robi większość języków) to mamy koncepcje znane z innych języków, ale nazwane inaczej, z inną składnią (hitem dla mnie jest unikanie powszechnie przyjętych nazw jak funkcja, domknięcie/closure, lambda, blok itp. i zamiast tego mamy jakieś zmienne typu code (ja wpierw pomyślałem, że to chodzi o jakieś makra, które manipulują kodem)

Czyli coś, co już na starcie się pozycjonuje jako język ezoteryczny przez nietypowe nazewnictwo i konstrukty (argumenty zapisane jako placeholdery). Zaciemnianie na siłę.

To, co mogłoby być ciekawe, to te tworzenie automatycznie całej rodziny zmiennych, ale do tego nie trzeba tworzyć całego języka. Wystarczyłoby stworzyć bibliotekę w istniejącym języku. No chyba, że faktycznie byłoby to mocno rozwinięte i dużo cukru składniowego, który by ułatwiał z tym pracę.

Ogólnie widzę tu jakiś twórczy zamysł (+samo tworzenie własnych języków to fajna zajawka), ale jeszcze niezbyt przemyślane i zbyt udziwnione. Może się mylę, ale sprawiasz wrażenie, jakbyś tylko C++ znał (o którym wspomniałeś) i dochodzisz samemu do tego, co już zostało dawno odkryte w innych, bardziej nowoczesnych językach, ale nie wiesz, że X się nazywa X, tylko nazywasz to po swojemu Y.

2

Ale ten temat już był dawno ogarnięty.

Języki programowania mają wyrażać jakieś informacje, i ta ilość jest stała. Możesz zmniejszyć obszerność kodu sprawiając że każdy symbol statystycznie będzie niósł ze sobą więcej informacji (ergo będzie ich więcej, i będą bardziej specyficzne) - tym samym zmniejszając obszerność kodu, ale również sprawiając że każdy kawałek będzie trudniejszy (aka. "syntax heavy languages"). Albo możesz iść w drugą stronę, co sprawi że każdy symbol będzie niósł mniej informacji, przez co kod będzie obszerniejszy ale każdy jego element "prostszy" (aka "syntax light languages").

Ale ilość informacji w kodzie zostanie taka sama.

0

Liczy się też biblioteka standardowa. Czasem problem nie leży w składni, tylko w tym, że brak w danym języku pewnych funkcji, które trzeba albo samemu sobie zaimplementować albo korzystać z zewnętrznych bibliotek.

Pytanie tylko, czy lepiej jest daną rzecz zaimplementować przez składnię, czy przez funkcję?

1
LukeJL napisał(a):

Liczy się też biblioteka standardowa. Czasem problem nie leży w składni, tylko w tym, że brak w danym języku pewnych funkcji, które trzeba albo samemu sobie zaimplementować albo korzystać z zewnętrznych bibliotek.

Wszystko jedno czy nazwiesz coś elementem języka czy elementem biblioteki standardowej. Czym jest biblioteka standardowa jak nie zbiorem już gotowych funkcji dzięki którym kod stanie się tym krótszy im będzie
bardziej gęsty w informacje?

1

Dzięki wszystkim za opinie. No to teraz potrzymajcie mi piwo ;)))
chomik-0.0.0

Wymaga C++ 17 z konceptami (nie korzystałem jeszcze z konceptów, ale tak na wszelki wypadek). Ja to kompiluję g++ 11.2.0. Oprócz tego wymaga bisona i flex'a. To na razie wersja pre-alfa.

Odpalcie np. chomik example4.chomik,

type boolean={false,true},
    place={Krakow,Warszawa,Wroclaw,Poznan,Gdansk};

variable (X:place) (Y:place) is_connected:boolean;  # it is possible to create attributes for tuples (like (X,Y) in this case)
    
expand(1);

let variable (X:place) (Y:place) is_connected = value boolean false;
let variable Krakow Warszawa is_connected = value boolean true;
let variable Krakow Wroclaw is_connected = value boolean true;
let variable Wroclaw Poznan is_connected = value boolean true;
let variable Warszawa Gdansk is_connected = value boolean true;

execute <print (X:place) "and" (Y:place) "IS CONNECTED =" <(X:place) (Y:place) is_connected>>;   # print all connections from X to Y

Ostatnia linijka jest ciekawa. Zawiera implicite pętlę po parach miejsc (X,Y) i odpala predefiniowaną funkcję print z następującymi parametrami:
X "and" Y "IS CONNECTED=" a

gdzie a jest wartością zmiennej o nazwie X Y is_connected. Polecenie let variable (X:place) (Y:place) is_connected = value boolean false; też zawiera pętlę, ale jeszcze nie działa. Pozostałe przypisania (bez niejawnych pętli) już działają.

Edit. Jeszcze zapomniałem - na styku parsera i reszty kodu są jeszcze memory leaki, bo parser nie niszczy wartości semantycznych jak należy. Poprawię to w następnej wersji.

0

Mam nową wersję.
chomik-0.0.1
Dwa przykłady są ciekawe, np. example4.chomik wypisuje to:

Krakow and Krakow IS CONNECTED = false 
Warszawa and Krakow IS CONNECTED = false 
Wroclaw and Krakow IS CONNECTED = false 
Poznan and Krakow IS CONNECTED = false 
Gdansk and Krakow IS CONNECTED = false 
Krakow and Warszawa IS CONNECTED = true 
Warszawa and Warszawa IS CONNECTED = false 
Wroclaw and Warszawa IS CONNECTED = false 
Poznan and Warszawa IS CONNECTED = false 
Gdansk and Warszawa IS CONNECTED = false 
Krakow and Wroclaw IS CONNECTED = true 
Warszawa and Wroclaw IS CONNECTED = false 
Wroclaw and Wroclaw IS CONNECTED = false 
Poznan and Wroclaw IS CONNECTED = false 
Gdansk and Wroclaw IS CONNECTED = false 
Krakow and Poznan IS CONNECTED = false 
Warszawa and Poznan IS CONNECTED = false 
Wroclaw and Poznan IS CONNECTED = true 
Poznan and Poznan IS CONNECTED = false 
Gdansk and Poznan IS CONNECTED = false 
Krakow and Gdansk IS CONNECTED = false 
Warszawa and Gdansk IS CONNECTED = true 
Wroclaw and Gdansk IS CONNECTED = false 
Poznan and Gdansk IS CONNECTED = false 
Gdansk and Gdansk IS CONNECTED = false 

Przykład example5.chomik jest trochę podobny -

let variable a (X:coordinate) (Y:coordinate) = value boolean false;
let variable a (X:inner) (Y:inner) = value boolean true;

execute <print "a" (X:coordinate) "," (Y:coordinate) "=" <a (X:coordinate) (Y:coordinate)>>;  

To teraz pytanie za sto punktów - czy dokonałem, Waszym zdaniem, rewolucji w informatyce, czy nie? Dla kolegi pytam ;)))

1 użytkowników online, w tym zalogowanych: 0, gości: 1