Efter att ha förstått tjusningen med funktionella språk (och nyttan av att emulera det i imperativ kod) så blev jag lite intresserad av LISP. Vad är det som är så bra med det då? EDIT: Helvete vad långt detta blev... Jag tror dock att jag besvarar din fråga, Roger. Not till mig själv: Enklaste sättet att döda en tråd är att skriva ett A4 långt inlägg. =) >>Vi får helt enkelt ett generellt sätt att göra vår egen syntax. Ruby har en del av LISPs funktionalitet, ja. Och det är verkligen en riktigt trevlig bit de har valt. Det är lite det som är grejen - i stort sett all ny funktionalitet som införts i "populära språk" sen 70-talet har varit saker som hela tiden varit möjliga eller redan funnits en bra stund hos LISP. För att visa att jag inte är ensam i min nyfunna övertygelse: >>känna att du har frihet. Lisp har <b>fler</b> såna grejer än de vanliga. <b>>men är det inte en illusion?</b>LISP-tips
"Hur fan kan de påstå att LISP är bättre än alla andra språk?", tänkte jag.
Jag visste ju att alla Turingkompletta språk i princip är likvärdiga.
Att de sen påstod att det var syntaxen som var det riktigt stora skälet till att LISP är bättre än allt annat gjorde inte min skeptism mindre. Syntaxen är ju dels nästan helt ointressant, den gör det lite lättare eller lite svårare att programmera. Dels så är ju knappast lisps syntax vacker...
"Lägg ihop 2 och 3": (+ 2 3)
Men jag tror jag har börjat förstå. Syntaxen är _så_ viktig, just för att den är _så_ reguljär. Vilket i sin tur betyder att den är _så_ lätt att skriva en kompilator eller interpretator för. Vilket i sin tur innebär att de grejerna som man har tillgång till kan göra _så_ mycket mer. Vilket i sin tur gör att språket blir vad man vill att det ska bli.
Eller uttryckt på ett annat sätt: eftersom syntaxen är som den är kan man generalisera allting till precis vilken nivå som helst. Och generalisering är bra i programmering.
Har hittat två riktigt bra siter om LISP, som jag tänkte tipsa om:
http://www.gigamonkeys.com/book/ Allmän introduktion, man lär sig "allt".
http://www.defmacro.org/ramblings/lisp.html Riktigt bra förklaring till varför Lisp är fett bra.Sv: LISP-tips
jag fick samma ide´för någon vecka sedan , "tjohoo jag ska lära mig vad functional languages är bra för... Haskell here I come" , läste lite om det och sure , lazy evaluation , trådsäkerhet etc är fina features.
men trådsäkerheten är ju mer ett resultat av att man inte kunda dela state med andra programdelar , så det går ju göra på precis samma sätt i andra språk.
lazy evaluation går ju oxo till viss del att emulera i .net med tex delegater..
vad är det som är lätt och smidigt att göra i tex lisp?
//RogerSv:LISP-tips
Det är det som är så svårt att förklara, och också det den andra länken visar.
Funktionell programmering i allmänhet har ju mycket stora fördelar, bland annat de du räknade upp. Det finns en del andra trevliga grejer:
Utveckla en "modul" (funktion, samling funktioner, whatever).
Till den bygger du snabbt en automatisk testare. Kör den som en del av kompilering, etc., så har du automatiskt testdriven programmering.
Vidare; eftersom en funktionellt skriven funktion aldrig rör någonting så blir buggrättning extremt enkel, du kan alltid studera varje del av programmet helt isolerat. Du behöver inte trace:a ner genom anrop för att se var problemen uppstår.
Du har också (åtminstone i Haskell) pattern matching. Man kan ju beskriva överlagrade funktioner/operatorer som pattern matching också. Men personligen tycker jag att saker som:
fac 0 = 1
fac n = (fac (n-1)) * n
eller
sum nil = 0
sum (cons list) = cons + (sum list)
är ganska eleganta (med viss reservation för syntaxen, minns den inte i detalj).
Jag menar inte att det inte går att göra i andra språk, det är bara det att det är enforced och blir jävligt snyggt i funktionella. Och behöver man trassla med tillstånd har man ju dels monader, och dels möjligheten att koppla koden till andra språk.
Vad gäller lisp tillkommer makron. Kort sagt så är Lisp-makron samma sak som C-makron fast deluxe. Och ja - jag vet hur förjävliga C-makron är. Det är så svårt att sätta fingret på exakt vad det är som gör lispmakron så bra.
Kan göra ett försök, jag snor ett exempel från länk två i inlägget ovan (eftersom jag är C++-programmerare så blir det kod av den sorten):
1. Låt säga att du arbetar mot en databas. Vanlig situation.
Du börjar med att skriva anrop mot databasen:
res = db.execute("SELECT CustomerName FROM Customers ORDER BY CustomerName");
2. Du inser efter 100 såna anrop att det är segt att skriva anropen på dessa sätt; framför allt felbenäget. Skriver du en bokstav fel så har du en bugg. Du vill flytta buggen bakåt, så att du istället får den i kompileringsfasen om det går. Så du gör istället klasser:
class result;
class Customers
{
result getCustomerNames();
//...
}
Det funkar jättebra; de tidigare anropen ser nu istället ut så här:
res = MyCustomers.getCustomerNames();
Så länge getCustomerNames() fungerar (och inte beror på state hos andra delar av programmet;) ), så kommer alla anropen fungera.
3. Det sker en förändring i databasen; ett fält tas bort, ett fält tillkommer, en ny tabell kommer. Du måste skriva nya databas-klasser, skriva kod till dessa, testa och debugga. Du tycker att det vore bra att få den koden automatiskt. Du hittar ett verktyg som automatiskt extraherar informationen som behövs för att göra dina klasser, sen gör funktionerna, sen skriver SQL-kod (och testar koden om det går).
Du lägger in verktyget så att det körs i samband med "build" eller "make", och får automatiskt ut de klasserna du behöver.
Har du felaktiga anrop i din övriga kod får du kompileringsfel. Jättetrevligt, allt är frid och fröjd.
Men hur är det med de verktygen som skriver koden åt dig?
Anledningen till att de tillkom är ju att man har skrivit fruktansvärda mängder sådana klasser. Det var liksom värt besväret att skriva en separat applikation för detta. Det hade varit trevligt om det fanns ett enkelt sätt att skriva sådana applikationer.
4. Vi har nu våra C-makron, och funderar på om vi skulle kunna utöka dem för våra databasbehov?
Vi låtsas att C-preprocessorn var lite smartare. Att den hade några fler kommandon.
#connect-db ("db", ...)
#tbl=get-tables
#for each t in tbl
class #t.name {
#t.fields
} ;
#next t
Detta skulle då <b>under kompileringen</b> ansluta till databasen, ta reda på alla tabeller, och skriva klasser för dem. Samma sak som verktygen i (3) ovan.
5. Vi inser att det är dumt att göra ett helt nytt preprocessorspråk bara för att ta reda på lite tabeller. Tänk om vi behöver något annat än just databasfunktioner?
Istället låter vi preprocessorspråket vara samma språk som det språk vi skriver i (C++ i mitt fall). Kapslar in det i någon syntax:
<->
std::cout << "Gimme a number";
std::cin >> max;
<->
for (int i = 0; i < <->max<->; i++)
std::cout << i;
Detta kommer vid kompilering fråga efter ett nummer och sen fylla i det. Svarar jag 5 så står det sen i koden:
for (int i = 0; i < 5; i++)
std::cout << i;
6. Nu kan vi då skriva vår vanliga kod som ansluter till databasen, tar fram tabeller, och generar klasser <b>i mitt program</b>:
<->
db.connect("db", ...)
result = db.getTables()
for(int i = 0; i < result.count(); i++) {
<->
class <->result[i].name<-> {
<->result[i].fields<->
}
<->
}
<->
Vilket i kompilering kommer att skapa en radda klasser, typ:
class Customers{
getCustomerNames();
getCustomerIds();
} ;
"Preprocessorkoden" kan vi vidare lägga i en "preprocessorfunktion", och till exempel ge argument. Vi kan då istället för att skriva ut hela koden där uppe skriva så här:
<->createDBScema("db1", "user", "pass", private_fields)<->
För att kunna styra så att vi får privata fält med getters, medan:
<->createDBScema("db2", "user", "pass", public_fields)<->
Skulle skapa klasser där fälten ligger öppna.
Vi får helt enkelt ett generellt sätt att göra vår egen syntax.
Ett annat exempel på hur en lisp-ifierad variant c++ skulle kunna se ut:
int funktion()
{
ensure_connection(db) {
db-operationer()
}
}
Vi skapar alltså en ny syntax, som i sin tur anropar kod, som i sin tur kollar om databasen har blivit ansluten. Har den inte det så ansluter vi och stänger när vi är klara, är den redan ansluten så gör vi ingenting.
Det blev mycket längre än jag trodde, hoppas nån läser... =)
Och hoppas framför allt att jag fick fram min poäng... =)
PS. Jag vet att det finns snarlika grejer i C#3. Men det är bara för databaser, och de är inbyggda. Vi vill ju hitta på egen syntax.Sv: LISP-tips
Sv: LISP-tips
ok men när sker det , "kompileras" lisp eller blir det bara det första som körs i din lisp app?
för isåfall går exakt samma sak att göra i Ruby, ruby kan både skapa kod under runtime (RubyOnRails kan göra exakt det du beskriver med klasser för tabeller) och Ruby har riktiga closures så du kan göra :
ensure_connection(db) {
db-operationer()
}
(vilket tom är med ruby_casing på metoderna :P)
så det du beskrev är ju inte riktigt något unikt för funktionella språk.
//RogerSv:LISP-tips
Men lisp är än mer. Du har hela tiden, både under interpretering och under kompilering, tillgång till AST:n till hela programkoden. Koden är data. Om du vill kan data vara kod.
Du skulle lätt kunna skriva en översättare från xml-syntax till s-expression;
<item>
<subitem>
x
</subitem>
</item>
->
(item
(subitem
x
)
)
Och sen lägga till item och subitem som nya symboler (makron eller funktioner). Sen läser du in din nya s-expression-fil i programmet (under körning alltså), och vips så är det kod istället om du vill. Sen kan du evaluera den koden. Du skriver ingen parser, du har alltid med dig en parser/kompilator. (Denna specifika detalj är inte heller unik för lisp. .NET har väl det, och jag tror att det finns experimentella Javavarianter).
Som sagt; det är mycket svårt att förklara det. Det är kombinationen av friheter som finns i lisp som gör att det blir så kraftfullt. Om du hade haft ett språk med objekt, men som saknar arv och casts, hade du känt dig begränsad. Inte för att det inte går att klara vissa saker ändå, men du måste skriva så mycket mer kod för det. Om du hade haft ett språk med objekt och arv men saknat if-satsen (som går att emulera med en while-sats) hade du inte heller känt att det var helt bra. Du måste ha alla grejer för att verkligen känna att du har frihet. Lisp har <b>fler</b> såna grejer än de vanliga.
Skillnaden mellan dålig och bra programmering handlar till stor del om mönster. En bra programmerare ser mönster, bryter ut dem, och gör bra kod. Men i ett "vanligt språk" kan vi inte bryta ut vissa mönster.
for(int i=0; i<n; i++)
är ett typiskt mönster i C-style-språken. En utbrytning av detta går att göra, men då är det i en funktion.
I en lispifierad variant av C hade det kunnat bli
for_range(i, 0, n){
}
<b>>så det du beskrev är ju inte riktigt något unikt för funktionella språk.</b>
Skulle egentligen inte kalla LISP ett funktionellt språk. Det är ett multi-paradigm-språk. Du kan programmera funktionellt, det är uppmuntrat, och språket är byggt enligt de principerna. Däremot _kan_ du mycket väl göra destruktiva operationer, om du verkligen vill. (Till exempel om du har vissa prestandakrävande områden av programmet.)Sv: LISP-tips
http://lispers.org/
Och den här är ju ofta citerad:
http://www.paulgraham.com/avg.htmlSv: LISP-tips
men är det inte en illusion?
säg att du skriver maskinkod i binärform
01101110101
11010101101
10101011101
10001011101
11101010110
10101010101
då kan du göra precis vad du vill , och allt med hjälp av bara 1or och 0or.
för mig ser lisp ut på samma sätt.
man kan säga att det är en dynamisk syntax som tillåter att man gör lite vad som och att syntaxen är striktare i andra språk.
men är det inte bara så att den strikta syntaxen i andra språk är förkortad smidigt syntaxsocker för specifika uppgifter?
medans lisp inte har något syntaxsocker för något alls.
vilket får det att framstå som att man kan göra vad som med lisps dynamiska syntax?
http://it.wikipedia.org/wiki/Quicksort#Lisp
kollar man där så är tex lispvarianten längre än c++ varianten
och varför vill man skriva en jämförelse som det här?
"(equal y x)"
Det är ju en klumpig syntax, och att kunna bygga nya features som ser ut att höra direkt till språket om de är lika klumpiga impar inte på mig iaf.
//RogerSv:LISP-tips
Jo, om vi ser till teoretisk beräkningsbarhet - ja.
Men lisp har en mycket enkel syntax, det är verkligen ett högnivåspråk. Jag skulle inte kalla den dynamisk, det är bara en enda typ av uttryck som är tillåten i hela språket; s-expressions, och den är mycket strikt på den punkten.
<b>men är det inte bara så att den strikta syntaxen i andra språk är förkortad smidigt syntaxsocker för specifika uppgifter?</b>
Nej. Om vi inte vill bygga en egen kompilator eller interpretator i ett annat språk, så skiljer sig lisp genom att vi kan förändra all kod under körning; vi kan skapa nya funktioner under körning. Vi kan gå runt på ast:n och göra vad vi vill med ett program.
Allt "vanligt" syntactic sugar kan vi själva göra i lisp utan problem. Om jag inte minns fel så är alla iterativa loopar (åtminstone i common lisp) skrivna i lisp.
<b>>http://it.wikipedia.org/wiki/Quicksort#Lisp
kollar man där så är tex lispvarianten längre än c++ varianten
</b>
För det första så använder C++ ett par STL-anrop, vilket gör att den blir kort. För det andra är lispvarianten skrivet på ett lite annorlunda sätt. Hade den varit skriven rekursivt (det normala när man kör med funktionella principer) hade den varit kortare. För det tredje är det en orättvis jämförelse.
Det är när man har gjort 20 olika sorteringsrutiner man skulle märka skillnad. Då hade vi faktoriserat ut principen för hur en generell sorteringsrutin fungerar, skrivit den som ett makro, och sen bara plockat ut det som skiljer de olika sorteringsrutinerna åt.
<b>>och varför vill man skriva en jämförelse som det här?
"(equal y x)"
</b>
För att det är ett ytterst reguljärt sätt att skriva det på.
Varför vill man skriva en jämförelse så här:
x==y
när man skulle kunna skriva så här:
x=y
Varför vill man skriva ett anrop med parenteser överhuvudtaget? C# borde skrivas om så här:
if x=y
print x
men det där är otydligt, vi kan väl skriva det lite mer engelskt (lite snyggare grammatik också):
If x=y then print x on the screen.
Eller?
Nej, det handlar om att vi förenklar språket så mycket som möjligt, att vi inte låter något vara inbyggt eller i övrigt eleverat över annan kod. = är en symbol. equal är en symbol. x är en symbol. Vill vi så kan vi säga att = är en instans av equal. och då skriver vi istället "(= x y)". Vilket i de flesta fall också stämmer.
Ett s-expression är extremt enkelt. Det som står först inom ett () är en funktion. Vi anropar den funktionen med allt som står till höger om den. Punkt slut. Därför vill vi inte blanda in trams som infix eller postfix. Inga speciella skiljetecken; vi har bara godtyckligt med whitespace mellan varje del.
<b>>Det är ju en klumpig syntax, och att kunna bygga nya features som ser ut att höra direkt till språket om de är lika klumpiga impar inte på mig iaf.
</b>
Jag gissar att du inte gillar xml något vidare?
s-expressions, (dvs. lisp) är xml, med små kosmetiska modifikationer för att slippa så mycket text. Jag kan lova; jag tyckte också att det såg rejält klumpigt och dumt ut. Sen förstod jag efterhand hur elegant syntaxen egentligen är.