Efter att blivit lite för inspirerad av currying/FP tråden så tänkte jag kolla lite på lisp. Det är mest på kul. för att se hur det fungerar. <b>>Men det är iaf helt sjukt vad lätt det är att utöka språket med nya features pga lisp syntaxen.</b> Nu har jag fått till allt möjligt roligt. Några detaljer: >>Den vedertagna, utan Ser snyggt ut, men jag kan inte riktigt koppla om du har en faktisk makro-implementation i och med dina expressions. I mitt fall är det egentligen bara funktioner. Hur hanterar Lisp variabel bindings i lambdas? Lite snabbt, och kanske fel, men är det inte en closure du pratar om här? Det kan nog vara lite språkförbisting från min sida. <b>>Hur menar du då?</b> Nu finns sourcen ute för de som är intresserade. Kul! Nu ni :-) (= del-res (delay (FooBar 1 2 3)) Ja det går, men inte med delay o force. Ge mig quote! :P Oki , jag är inte exakt 100% jur just quote grejorna fungerar. Jepp, backquote är det som gör det du säger, vanlig quote gör inget speciellt :) Nu börjar alla symboler o elände sjunka in iaf :) . är intressant när du har listor uppbyggad av cons-celler, bara då ;)Hur Lispig är jag?
Så av ren tristess så började jag bygga en liten lisp tolk, men eftersom jag aldrig kodat lisp så vet jag inte hur rätt det blev :-P
Jag blev iaf rätt nöjd med resultatet men tänkte kolla med ni som har pejl på Lisp om det är några uppenbara o-lispigheter i min syntax/struktur.
(ok jag ignorerade helt Lisps inbyggda funktioner och petade in egna, men flöde och struktur tror jag blev rätt.. )
Här är iaf mitt första lilla Roger-Lisp winforms program :-)
(defun ButtonClick (sender event-args)
(
(print event-args)
(print sender)
(set form Text (concat 'Hello Click:' (call sender ToString)))
)
)
(let form (new Form )) /*Form är ett alias för system.windows.forms.form*/
(set form Text 'Hello lisp') /*set = macro för call med "set_" prefix (.net set prop)*/
(set form Width 500)
(let controls (get form Controls)) /*get = macro för call med "get_" prefix (.net get prop)*/
(for y 0 5
(
(for x 0 5
(
(let button (new Button))
(set button Text (concat 'button:' x '.' y))
(call controls Add button)
(set button Left (* x 80))
(set button Top (* y 50))
(set-event button Click (address-of ButtonClick))
)
)
)
)
(call form Show)
Sv:Hur Lispig är jag?
Men det är iaf helt sjukt vad lätt det är att utöka språket med nya features pga lisp syntaxen.
(pga att allt bara är funktioner som returnerar ett värde till stacken dvs)
Tog ca 5 minuter att få lambdas att fungera..(defun Kumo (a b)
(
(print (+ a b))
)
)
(defun DelegateTest (func)
(
(print 'before delegate')
(func 2 3)
(print 'after delegate')
)
)
/* skapa en lambda */
(let my-lambda (lambda (a b) (print 'my freaking lambda')))
/* skapa en delegat till Kumo funktionen */
(let test (delegate Kumo))
/* anropa delegatetest med delegaten till kumo */
(DelegateTest test)
/* anropa delegatetest med lambdan */
(DelegateTest my-lambda)
Sv: Hur Lispig är jag?
Kolla min tråd typ ett år tillbaks, när vi diskuterade detta. Du sa något i stil med "varför skulle jag vilja skriva i ett språk som har en så hemsk syntax", och jag svarade "det är just syntaxen som är så smart, du kan förändra språket". Vad var det jag sa... =)Sv:Hur Lispig är jag?
tex:
(lambda (arg1 arg2 etc) (body))
(lambda (body)) //får en default param "item"
(let print (lambda (text) ( gör något annat ))) //definiera om funktioner
(let print (some-delegate))
Men, den största utmaningen verkar helt klart vara den gamle bästen "yield"
Finns det något fusk för att fåtill yield, eller är man så illa tvungen att göra precis som .net kompilatorerna gör och skapa en generator/enumerator.
det känns ju som det kommer krävas väldigt speciell parsning för just de bitarna..
Allt annat är ju 100% stackbaserat och behöver inte känna till tidigare steg i stacken.
i det här fallet krävs det att tex for-loopar hanteras på andra sätt osv.Sv: Hur Lispig är jag?
-address-of borde du väl inte behöva?
-i lisp är inte stilen
(...
(...
...
)
)
Den vedertagna, utan
(...
(...
... ))
Ser dumt ut först, men också något man vänjer sig vid - det är bättre med färre rader.
Sen undrar jag om du har fått ordning på makron än?Sv:Hur Lispig är jag?
Jag börjar vänja mig vid den syntaxen nu ;-)
>>address-of borde du väl inte behöva?
har inte tänkt på det innan , men du har helt rätt.. alla funktioner är av samma typ som det jag får ut av min (address-of funktion..
>>Sen undrar jag om du har fått ordning på makron än?
Jag måste erkänna att jag inte kollat exakt hur de ser ut i lisp , bara skummat och hemkokat lite.
men ja, jag har viss form av macrostöd
tex:
(defun ++ (*var) (let var (+ 1 var)))
(defun += (*var val) (let var (+ val var)))
*arg ger en ref variabel a'la byref i vb / ref i c#
så exemplet ovan gör att jag kan skriva: (++ i) eller (+= i 8) för att öka "i"
jag har ett antal olika argument-typer
"arg" vanlig stackbaserat arg
"*arg" ref param
":arg" literal , dvs consumern måste skicka en literal som ser exakt ut som ":arg"
"&arg" expression , lite som en lambda fast utan params
Man kan även göra mycket mer avancerade macro funktioner än ++.
(defun vb-for (*var := start :to stop &body :next)
(
(let var start)
(while (<= var stop)
(body)
(++ var))))
kan anropas med:
(vb-for a = 10 to 20
(print (concat 'hej ' a))
next) //mhmm imba vb lispbastard..
//ok någon form av do .. while variant kanske är mer verklig
(do ((print i)(++ i)) while (< 10 i))
Så det gör att göra rätt vilda saker.
Tex. list comprehensions:
(defun where (&predicate items)
(
(let res (list))
(foreach item items
(if (predicate) (list-add res item) ()))
(res))) //return res .. nej , har inte stöd för yield, som vore mycket snyggare
(defun select (&selector items)
(
(let res (list))
(foreach item items
(list-add res (selector)))
(res)))
(foreach item (select (concat 'transformed: ' item '!')
(where (> (get item Length) 3)
(list 'foo' 'bar' 'roger' '.net' 'lisp')))
(print item))
obs. det går lösa list comprehensions med lambdas istället för att skicka &body block som evalueras.
dock blir koden lite kortare med &body.
Sv: Hur Lispig är jag?
Kolla på backtick etc. här:
http://gigamonkeys.com/book/macros-defining-your-own.html
så ser du nog själv. Observera att man använder & på ett annat sätt än du, det indikerar en symbol för lisp-motorn (och det finns bara några). Sen är din :arg inte riktigt samma heller, osv.
Hela boken (en av de bättre) ligger på:
http://gigamonkeys.com/book/Sv:Hur Lispig är jag?
Fast med ett koppel olika arg typer.
Så jag har ingen macro expand tjossan.
Har inte råkat ut för något jag inte kan lösa på det sätt jag har det nu.
(förutom att vara 100% lisp kompatibel :p , men det är inte högprio atm)Sv:Hur Lispig är jag?
tex:(defun MyFunc (arg)
(lambda () (* arg arg)))
där returnerar funktionen en lambda med 0 parametrar , men vars body använder en variabel/param från det context som skapade den.
Jag löste det genom att:
1) clona grafen för lambdans body när lambdan skapas
2) omvandla alla identifiers i den cloanade bodyn till konstanter.
så om jag skulle göra såhär:
(MyFunc 123)
Så skulle lambdan som returneras se ut som (lambda () (* 123 123 ))
så på så sätt så kan lambdan gå mot de värden som gällde när lambdan skapades.
Men jag antar att det inte fungerar exakt så i riktiga lisp?
Sv: Hur Lispig är jag?
I så fall har nog det du skriver precis samma beteende. Fast det känns fel att behöva manuellt göra kloningen på parser/kompilator/interpretator-sidan, det "känns" som något som bara borde funka.Sv:Hur Lispig är jag?
i .NET så är closures delegater, och lambdas är syntaxsocker för att skapa anonyma delegater.
Så för mig så är båda delegater, men jag säger nog lambda om delegaten skapades via lambda syntax.
Men iaf. om jag gör:
(let func HelloWorld)
Så handlar det ju om en ren delegat, och ingen cloning behöver ske, eftersom HelloWorld inte behöver binda mot några variabler i current context, den har ju sina egna lokala variabler/params etc.
Medan exempelt i förra posten kräver att man clonar för att kunna binda koden mot variabler som kan gå ur scope.
>>Fast det känns fel att behöva manuellt göra kloningen på parser/kompilator/interpretator-sidan, det "känns" som något som bara borde funka.
Hur menar du då?
I de felsta fall så ska ju inte kodblock clonas, det är ju bara när man är tvungen att binda värden i ett visst scope som man behöver det.
Så min "lambda" funktion som är skriven i C# , tar helt enkelt bodyn för lambdan och clonar den och ersätter mjuka värden med konstanter.
Det är inte på något sätt att jag måste speca själv i parsern att det ska clonas på ett visst ställe i koden. det sköts 100% av själva lambda funktionen.
Har även börjat koda delar av språket i sig själv.
Dvs. många core funktioner är skrivna så, det känns nästan som den häftigaste biten so far.Sv: Hur Lispig är jag?
Det var bara en känsla att det inte skulle vara ett speciellt tungt jobb, men det verkar ju som att det inte är det heller.
<b>>Har även börjat koda delar av språket i sig själv.
Dvs. många core funktioner är skrivna så, det känns nästan som den häftigaste biten so far.</b>
Yes, det är vansinnigt vad man kan göra. Nu, med något klarare skalle, vad du behöver för "riktiga makron":
Kan du ta emot en kodrad, och göra operationer på den?
Alltså typ (med backticks etc. på rätt ställen):
(reverse (5 3 +))
=>
(+ 3 5)
=>
8
Det här är det riktigt fantastiska steget. I och med detta blir språket det bästa som finns. Det blir busenkelt att skriva en LISP-tolk i LISP, och din C#-kod blir bara en bootstrapper. =)Sv:Hur Lispig är jag?
(måste varna att det är väldigt quick n dirty eftersom det hela bara är på kul.. så kommentarer o felhantering lyser med sin frånvaro)
http://rogeralsing.com/2008/01/30/lisp-debugger/
Har byggt en liten debugger för det också :-)Sv: Hur Lispig är jag?
(Men jag måste säga att bildkvaliteten på screenshoten var bland det värsta jag har sett =) )Sv: Hur Lispig är jag?
nu har jag stöd för delay/lazy eval.
och currying :-P
Currying:
(func CurryTest (a b c)
(print (format "stuff {0} {1} {2}" a b c)))
(= a (CurryTest 1))
(= b (a 2))
(b 3)
(= c (CurryTest 1))
(c 2 3)
(CurryTest 1 2 3)
(alla anropen ger samma resultat där)
Delayed:
(= del-res (delay (FooBar 1 2 3))
(= res (force del-res)
(print res)
VM i meningslösa aktiviteter :-)
Sv:Hur Lispig är jag?
(= res (force del-res)
(print res)
Det här vill jag ha lite mer info på. Innehåller del-res LISP-koden nu?
Så att du i princip kan skriva följande?
(= del-res (delay (1 2 3 FooBar))
(= res (force (reverse del-res))
(print res)
och det motsvarar
(print (FooBar 3 2 1))
Fett i så fall! =)Sv: Hur Lispig är jag?
Delay skapar en delegat, och den binder de symboler som ingår i uttrycket till snapshots, dvs, betydelsen av dem sparas i delegaten, för att inga sidoeffekter ska kunna uppstå.
Men, tillbaka till ditt exempel.
Det går att göra med:
(= a '("hej" print))
(= a (reverse a))
(',a) Sv:Hur Lispig är jag?
Quote är ', fast på längre format:
'(kossa 1 2 3)
och
(quote (kossa 1 2 3))
är samma sak. Den sista är trevlig när du vill skriva macros. Och appropå det, backquotes vore trevligt också, som ', men är istället ´. Om man sedan sätter kommatecken framför symboler i uttrycker så evauleras dem. Också trevligt för macron.Sv: Hur Lispig är jag?
jag tror jag har vissa bitar rätt nu.
' = quote = returnerar en clone av ett expression, där alla subexpressions med "," prefix har blivit evaluerade.
tex:
(=somevar "hej")
(= res '(print ,somevar)
ger:
(print "hej")
och då kan jag även göra:
',res
för att evaluera "res", eftersom "," efter "'" tvingar evaluering.
Så långt tror jag att det är rätt.
men vad gör backquote ´ och . då?
[edit]
jag verkar ha snurratihop det.. (?)
det är backquote som gör det jag skrev ovan?
dvs. jag har fel tecken i min implementation för det.Sv:Hur Lispig är jag?
Sv: Hur Lispig är jag?
Så har jag börjat fixa så det är liter kompatibelt med common lisp.
men iaf, för att återknyta till Niklas post:
(= a '("hello pellesoft" print))
(print a)
(= a (reverse a))
(print a)
(eval a)
ger en output av:
("hello pellesoft" print)
(print "hello pellesoft")
hello pellesoft
och
(= a `(4 5 6))
(print a)
(= b `(1 2 3 ,a))
(print b)
(= b `(1 2 3 ,@a))
(print b)
ger en output av:
(4 5 6)
(1 2 3 (4 5 6))
(1 2 3 4 5 6)
Nu känns det bara som att "." är den symbol jag inte har grepp på.
vad gör den/innebär den?
Jag vet vad cons celler är och att om man gör.
(min tolk är _inte_ baserad på cons celler, den har riktiga platta listor just nu)
(cons 1 2)
så ska man få (1 . 2) som resultat.
och jag vet att 2an då ska vara lagrad i cdr delen av cons cellen.
dvs:
[ 1 | 2 ]
och inte :
[ 1 | [ 2 | null] ]
som det blir normalt sett.
men vad innebär själva "."en ?
[edit]
Nu fungerar riktiga macron...
(defmacro swap (a b)
`(progn
(= temp ,a)
(= ,a ,b)
(= ,b temp)))
(= x 10)
(= y 55)
(print x y)
(swap x y)
(print x y)
*känner mig extra geekig nu.. kanske borde tattuera in en lambda på axeln också ;-) *
Sv:Hur Lispig är jag?
Hur som haver: När man skriver ut en cons-cell/skriver den i quotad form, och den är ett "punkterat par", dvs. att cdr (andra behållaren i cellen) inte innehåller nil/en referens till nästa cell, utan någonting annat, t.ex. en symbol eller ett tal, då är den ett punkterat par.
Ex:
'(a b c) är inte punkterat par
'(a b) är inte punkterat par
'(a . b) är dock punkterat par
(cons a b) är punkterat par, blir samma som '(a . b)
Punkten visar alltså att istället för att skapa en lista så vill man "manuellt" fylla car och cdr helt enkelt. Vilket du inte har någon nytta av eftersom att du inte har cons-celler =)