"Företagsboendeförmedlare" | Min Überkill Dator: Processor: Intel Pentium P5 66 Mhz OC | Moderkort: ASRock P4I65G | Minnen: 2st Samsung 128MB PC133 | Grafikkort: Canopus GeForce 256 DDR | Lagring: IBM 350 4,4 MB | Operativsystem: DOS/360 | Chassi: Mercury Full-Tower ATX Chassis |
OOP vs funktionell programmering - Dina erfarenheter?
Visa signatur
Skrivet av AplAy:
Först och främst så är jag fullt medveten om att frågan "Vad är bäst: OOP eller funktionell programmering?" är som att fråga om en bil, ett flygplan, eller en båt är bäst. Allt handlar om användningsområden, syften och vilka kompromisser man är villiga att ta.
Jag såg på följande YT-klipp (lägg ingen större vikt i att det är just TS som används som exempel, kunde lika gärna varit C++ för den delen även om detta oxå påverkar slutresultaten):
https://www.youtube.com/watch?v=fsVL_xrYO0w
Min diskussionsmässiga sakfråga är snarare vad du har för erfarenheter av att koda med OOP-utgångspunkt kontra funktionell programmering och vilka "aha!"-upplevelser du har haft som följd av detta vilket då gett dig din egen personliga "för- och nackdelar" för OOP kontra funktionell programmering.
Jag föreställer mig att funktionell programmering lämpar sig för program som mer till synes är mer linjärt procedurmässiga, alltså att du skall gå från ett steg till ett annat och sedan bli färdig. Medan OOP lämpar sig kanske bättre för program som måste nyttja dynamiska system såsom spel med spelmotorer, olika klasser såsom bland annat spelare, fiender, samverkande spelmekanismer, nivåer, osv.
Med det sagt så går det säkert att göra linjära program med OOP och spel med hjälp av funktionell programmering. Frågan är vad som lämpar sig bäst för vad och vilka kompromisser man är villiga att ta då i och med oundvikliga nackdelar med respektive vald programmeringsform?
Nu är jag ingen expert men från vad vi fick lära oss i skolan så är det lite tvärt om. Funktionell programmering lämpar sig faktiskt väl för distribuerade system/multicore just eftersom man undviker att olika processer är inne och pillar i varandras register.
Jag ser det ungefär så här:
Rent funktionella språk som tex Haskell tvingar en att tänka annorlunda. Värt att lära sig om man har tid och lust.
Vissa program lämpar sig väl för funktionell programmering tex inom concurrent/parallell programmering. Tex. Erlang skapades just för att göra telecomsystem säkrare och snabbare.
Vissa problem löses lättare med ett funktionellt approach, tex. manipulation och traversering av träd- och grafstrukturer. Alltså oberoende av språk (så länge det stödjer rätt funktionalitet).
Citera flera
Citera
(5)
Första gången jag hörde talas om funktionell programmering var i den allra första kursen på Chalmers: Introduktion till funktionell programmering. Det var väldigt förvirrande för mig, som bara hade suttit och skrivit överdrivet imperativ JavaScript-kod, att få höra saker som "det finns inga [muterbara] variabler i Haskell", för dittills hade sidoeffekter varit mitt enda sätt att uttrycka mig.
Efterhand tog jag till mig det funktionella tankesättet, och jag ser än idag, efter fem år på högskolan och tre år som professionell mjukvaruingenjör, den kursen som den allra viktigaste milstolpen i min utveckling som programmerare. (På en hedervärd andraplats: när jag lärde mig använda Git på riktigt.)
Innan jag går in på mina erfarenheter i frågan skulle jag vilja ta upp något som bara nämndes väldigt implicit i förbigående i videon, nämligen att det finns två distinkt olika betydelser av "funktionell programmering":
Funktioner är rena (pure). De muterar inte tillstånd och de returnerar alltid samma sak om de appliceras på samma argument.
Funktioner är värden. De kan skickas som argument, returneras, lagras i datastrukturer osv, precis som vilka andra värden som helst.
Båda dessa egenskaper kan dessutom beskriva såväl programmeringsspråk som [delar av] enskilda program:
I vissa språk är renhet representerat i typsystemet (t ex Haskell), men i "alla" språk går det att skriva rena funktioner om man vill.
I vissa språk är funktioner värden (t ex Haskell, TypeScript, Python), men programmeraren kan i regel välja att bry sig mer eller mindre om det. I vissa språk är funktioner inte värden (t ex Java).
Skrivet av AplAy:
Min diskussionsmässiga sakfråga är snarare vad du har för erfarenheter av att koda med OOP-utgångspunkt kontra funktionell programmering och vilka "aha!"-upplevelser du har haft som följd av detta vilket då gett dig din egen personliga "för- och nackdelar" för OOP kontra funktionell programmering.
Jag föreställer mig att funktionell programmering lämpar sig för program som mer till synes är mer linjärt procedurmässiga, alltså att du skall gå från ett steg till ett annat och sedan bli färdig. Medan OOP lämpar sig kanske bättre för program som måste nyttja dynamiska system såsom spel med spelmotorer, olika klasser såsom bland annat spelare, fiender, samverkande spelmekanismer, nivåer, osv.
Jag tycker att du är inne på rätt spår: min uppfattning är att det ofta är enklare att använda funktionell programmering i vad du beskriver som "mer linjärt procedurmässiga" program än i "program som måste nyttja dynamiska system".
Det gäller dock att inte lura sig själv att tro att ett program måste vara antingen helt funktionellt eller inte funktionellt alls. Jag skriver knappt ett enda program utan att använda funktionella idiom och principer i stora delar av koden, trots att sidoeffekter ofta också är centrala. Ett bra exempel är Userscript Proxy, som består av ett antal moduler som nästan bara innehåller rent funktionell kod, samt en "huvudmodul" som utför alla sidoeffekter.
Vidare vill jag nämna att "OOP vs FP" är en falsk dikotomi: det finns paradigmer och idiom som inte faller under varken OOP eller FP, och de två principerna kan kombineras. Jag skiljer hellre mellan imperativ och deklarativ programmering, med satser och sidoeffekter respektive uttryck och värden som huvudsakliga byggstenar.
Men – till saken. I min erfarenhet är objektorienterad programmering i hög grad ett antipattern. Det framställs som "enkelt", "det blir som vanlig engelska" och liknande, men jag menar att folk lurar sig själva. OOP-kod må se enkel ut, men att skriva korrekt och robust objektorienterad kod är, anser jag, avsevärt svårare jämfört med FP-dito, av flera anledningar:
OOP-kod är svår att resonera och tänka kring. Det finns sällan något tydligt dataflöde, utan sidoeffekter tenderar att användas så mycket att man aldrig kan lita på någonting. Det är svårt att avgöra om en refaktorering påverkar programmets betydelse eller inte, och så vidare.
OOP-kod kan inte dra särskilt stor nytta av typcheckning, eftersom sidoeffekter är mycket svårare att representera i ett typsystem än vad värden är. En typsignatur som
void f()
i Java säger endast attf
rimligen har sidoeffekter, men ingenting om vilka de är. Jämför det medf :: String -> Bool
i Haskell, som indikerar attf
sannolikt mappar varje sträng till ett boolskt värde – varken mer eller mindre.OOP-kod är svår att testa. Sidoeffekter är alltid svårare att testa än mappningar mellan mängder – ibland lite svårare, ibland mycket svårare, men aldrig lika enkla. Tester för rent funktionell kod är aldrig mer komplicerade än
expect(f(x)).toBe(y)
.
Slutligen vill jag understryka följande: förutom att det ofta är hjälpsamt att använda funktionell kod i en imperativ kontext (som i mitt exempel med Userscript Proxy ovan) kan den mest naturliga implementationen av en ren funktion mycket väl vara imperativ. Följande vore till exempel ett utmärkt sätt att implementera mappning över en lista i TypeScript om inte Array.prototype.map
redan fanns:
function map<X, Y>(f: (x: X) => Y, xs: readonly X[]): Y[] {
const ys = []
for (const x of xs) {
ys.push(f(x))
}
return ys
}
Att härma Haskells rent funktionella implementation vore krystat, långsamt och allmänt dåligt:
function map<X, Y>(f: (x: X) => Y, xs: readonly X[]): Y[] {
if (xs.length === 0) {
return []
}
const [x, ...rest] = xs
return [f(x)].concat(map(f, rest))
}
Till sist vill jag säga att jag inte tycker funktionell programmering borde ses som något frivilligt som man lika gärna kan strunta i. Alla programmerare borde lära sig det.
Citat:
Med det sagt så går det säkert att göra linjära program med OOP och spel med hjälp av funktionell programmering. Frågan är vad som lämpar sig bäst för vad och vilka kompromisser man är villiga att ta då i och med oundvikliga nackdelar med respektive vald programmeringsform?
Jag har faktiskt länge planerat, och för ett tag sedan påbörjat, en omskrivning av min Achtung, die Kurve!-klon från JavaScript till Elm. Även om man bortser från de enorma fördelarna med ett starkt, statiskt, expressivt, nominellt typsystem kan jag redan göra två nyckelobservationer:
Det var lättare att komma igång och skriva spelet med OOP.
Det är lättare att skriva spelet korrekt samt att lägga till mer funktionalitet med FP.
Det ska bli väldigt spännande att bli klar med Elm-versionen, då jag aldrig tidigare skrivit ett helt funktionellt realtidsprogram. Ska bara finna den där gnistan som så lätt falnar när man sitter med kod hela dagarna.
Visa signatur
Skrivet med hjälp av Better SweClockers
Citera flera
Citera
(6)
Skrivet av AplAy:
Min diskussionsmässiga sakfråga är snarare vad du har för erfarenheter av att koda med OOP-utgångspunkt kontra funktionell programmering och vilka "aha!"-upplevelser du har haft som följd av detta vilket då gett dig din egen personliga "för- och nackdelar" för OOP kontra funktionell programmering.
Måste också ta upp en väldigt oväntad insikt – och i högsta grad en aha-upplevelse – som jag fick när jag läste kursen Types for Programmes and Proofs, nämligen Curry–Howard-isomorfismen och dess betydelse.
"WTF?"
Curry–Howard-isomorfismen säger att typer är (logiska) propositioner och program/värden är deras bevis. Det vill säga, lika gärna som att "halloj"
har typen sträng kan man tänka att "halloj"
är ett bevis för [att det finns] strängar. Fler exempel:
Produkttypen
(A, B)
motsvarar den logiska konjunktionen "A och B".Summatypen
A | B
motsvarar den logiska disjunktionen "A eller B".Funktionstypen
A → B
motsvarar den logiska implikationen "om A, så B". Tillhandahåller man en funktion med den typsignaturen säger man ju "Ge mig ett [bevis för] A, så lovar jag att dig ett [bevis för] B".Den tomma typen (även känd som bottentypen;
Void
i Haskell,never
i TypeScript) motsvarar den logiska absurditeten ⊥, dvs det uppenbart falska påståendet. Den tomma typen har inga värden, liksom den absurda propositionen inte har några bevis.Enhetstypen (som bara har en enda medlem, ofta betecknad
()
) motsvarar det trivialt sanna påståendet. Typenvoid
i imperativa språk påminner namnet till trots framförallt om enhetstypen, men den delar också egenskaper med den tomma typen.
Förutom att detta kan användas för assisterad bevisföring med verktyg som Agda och Coq har det för mig framförallt den filosofiska konsekvensen att man kan vara mer eller mindre ärlig när man skriver kod. Om jag skriver en funktion f
av typen A → B
så är typsignaturen ett löfte om att f
, givet ett [bevis för] A
, kommer producera ett [bevis för] B
. Att då låta f
till exempel returnera null
eller kasta ett fel är att bryta det löftet. Det betyder inte att det nödvändigtvis alltid är dåligt att göra så, men det är bra att vara medveten om vad det är man gör och inte ljuga hejvilt utan eftertanke.
Omvänt försöker jag i samma anda att utfärda så specifika löften som möjligt: skriver jag en funktion som returnerar antingen "foo"
eller "bar"
vill jag typiskt inte att dess returtyp (i TypeScript) ska vara string
. Det vore förvisso inte att ljuga, men det vore att inte berätta hela sanningen samt att underdriva värdet av mitt bevis (min funktion). Om jag däremot ser till att returtypen är "foo" | "bar"
maximerar jag mängden information som typcheckaren har tillgång till, och därmed dess förmåga att hjälpa mig/andra som använder funktionen.
Visa signatur
Skrivet med hjälp av Better SweClockers
Citera flera
Citera
(1)
- Dagens fynd — Diskussionstråden55k
- Lg oled problem5
- Vad är rätt fiberkabel?7
- 3060 eller 3070 som present till spelande bror?9
- C++ och dess framtid att programmera minnessäkert - Hur går utvecklingen?1,6k
- Stor offentlig leverantör hackad81
- SweClockers - Marknadsreferenser (läs första inlägget innan du postar!)14k
- Lenovos handhållna Legion Go 2 kan avtäckas på IFA-mässan1
- ShowCase: Phanteks nya chassin och kylning från insteg till lyx16
- Signalbrus Steelseries2
- Säljes Seasonic G12 M 650w 80+ Gold
- Säljes Google Pixel 10 Pro - Ny oöppnad
- Säljes Geist Totem Split Bluetooth Tangentbord DIY-kit
- Säljes Moderkort, minne, cpu
- Säljes Bra burk (med eller utan gpu)
- Köpes Köpes: Mini-ITX moderkort - Intel 1700 - DDR4
- Säljes LG C3 65 inch OLED + Philips Hue Gradient Lightstrip 65 inch
- Säljes i5 10600KF, ROG STRIX B460-F GAMING.
- Säljes Govee Smart Gaming Ljuspaneler
- Köpes Sökes: Raijintek Morpheus 8069
- Lenovos handhållna Legion Go 2 kan avtäckas på IFA-mässan1
- ESET hittar skadeprogram som skriver sig självt17
- AMD går på djupet i RDNA 410
- Sapphires moderkort närmar sig lansering20
- Quiz: Vad kan du om gamingskärmar?65
- MSI: 533 dagar senare - knappt någon OLED-inbränning113
- Intels Nova Lake-S nära färdigställda31
- Bättre stöd för Bluetooth-headset i Windows 1141
- AMD Ryzen Threadripper 9980X & 9970X – bäst i klassen19
- Här är de fem första rekryterna till Battlefield 6: Slaget14
Externa nyheter
Spelnyheter från FZ
- Battlefield 6 – ny pc-trailer och detaljerade systemkrav idag
- Fragzone-fredag – Hetast i höst, bäst någonsin och BF6-slaget! idag
- Snart på PS Plus: Psychonauts 2, Stardew Valley, Viewfinder idag
- FZ High Score-säsongen i mål – Vi har en... nej, två vinnare igår
- Bethesda antyder riktiga rymdresor i Starfield igår