Permalänk
Medlem

Funktionell programmering

Har hållit på med Java, Javascript, lite Kotlin, lite Python ett tag nu och tycker jag har fått bra koll på OOP. Tänkte jag skulle prova något nytt så jag köpte den nya boken från Manning "Functional programming in Kotlin".

Boken är tydligen en omskrivning av en populär bok "Functional programming in Scala" med samma förklaringar och övningar förutom där Kotlin inte tillåter det.

I a f så är jag runt 25% in i boken och numera klarar jag bara runt hälften av övningarna utan att tjuvkolla på lösningarna. Resten har jag ofta ingen aning om hur jag ska lösa (som en övning att implementera en mapfunktion för en trädstruktur).

Blir det här tänket som skiljer sig så mycket från OOP någonsin lättare att resonera kring?
Hur mycket stöter man på FP ute i riktiga världen?

Permalänk
Medlem

Glöm detta.

EDIT: Jag hade fel. Andra får ta över nu.

Permalänk
Medlem

@ronnylov: Tackar!

Jag har inte testat ett rent funktionellt språk men jag upplever Kotlin har ett starkt stöd för det eftersom det är väldigt inspirerat av Scala, det finns t ex tail call optimization till skillnad från Java så man kan undvika stack overflow i de rekursiva anropen. Största problemet jag har med tänket är att boken förbjuder en att använda loopar utan allting ska ske via dessa rekursiva anrop för att inte skapa "side effects".

Permalänk
99:e percentilen
Skrivet av ronnylov:

Programspråk som C är enbart funktionell programmering.

Håller jag inte med om överhuvudtaget.

Jag anser att "funktionell programmering" har två distinkt olika betydelser, där såväl språk som enskilda program något förenklat kan anses tillhöra den ena, båda två eller ingen av dem:

  • Funktioner är värden.

    • För språk: Funktioner kan skickas som argument, returneras, tilldelas osv precis som heltal, strängar eller vilka andra värden som helst. Exempel: Haskell, JavaScript, Python, Kotlin.

    • För program: Ovanstående görs och dras nytta av i stor utsträckning. Går att göra i språk som tillåter det.

  • Funktioner är rena (pure).

    • För språk: Det går bara att skriva rena funktioner. Exempel: Haskell, Agda.

    • För program: Rena funktioner skrivs och dras nytta av i stor utsträckning. Går att göra, åtminstone i viss grad, i så gott som alla språk.

C är definitivt inte funktionellt i den andra betydelsen, och jag tycker verkligen det är att ta i att kalla det funktionellt i den första betydelsen, trots funktionspekare.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem
Skrivet av ronnylov:

Programspråk som C är enbart funktionell programmering. Sedan finns det programspråk där man inte måste använda OOP även om stödet finns. Men att enbart använda funktionell programmering i programspråk gjorda för OOP känns lite konstigt. Osäker på hur Kotlin är tänkt att användas. Men det är bra att du lär dig olika sätt att använda det.

Från att ha börjat med C kändes övergången till OOP när jag testade C# som väldigt omständig. Förstod inte varför man skulle se allting som objekt istället för att fokusera på vad programmet skulle utföra. En gång i tiden fanns inte OOP. Men använder OOP ganska friskt nu när jag programmerar i Dart och Flutter. Har ändå nytta av funktionell programmering när jag skapar metoder inuti klasserna. Kan t.ex. skapa en klass med enbart statiska metoder för att bunta ihop funktioner som är bra att kunna anropa från andra klasser.
Så det är väl bara att tänka som att du skapar metoder? Som jag förstått saken är funktion samma sak som en metod. Data får man lagra i variabler. Struct och/eller arrayer i kombinationer om man vill bunta ihop det. Sedan får man väl skicka runt data eller pekare till data mellan olika funktioner. Blir väl som att hela programmet består av en enda klass och man fokuserar kanske mera på programflödet.

Jag tror det är nyttigt att testa olika sätt att tänka när man programmerar och därför testa olika programmeringsspråk som hjälper till att lära ut sättet att tänka. Men blir lätt insnöad på det gamla vanliga annars. Kanske finns det effektivare sätt att lösa ett problem när man vet hur man kan angripa det på olika sätt.

C är INTE funktionell programmering, utan procedurell.

Visa signatur

Core i7 7700K | Titan X (Pascal) | MSI 270I Gaming Pro Carbon | 32 GiB Corsair Vengeance LPX @3000MHz | Samsung 960 EVO 1TB

Permalänk
Medlem
Skrivet av Nioreh83:

C är INTE funktionell programmering, utan procedurell.

Ojdå. Trodde det var samma sak? Man kan göra funktioner i C. Berätta skillnaden!

Edit: Verkar som jag missförstått alltihopa....

Permalänk
Medlem
Skrivet av ronnylov:

Ojdå. Trodde det var samma sak? Man kan göra funktioner i C. Berätta skillnaden!

Edit: Verkar som jag missförstått alltihopa....

Att man kan göra funktioner är inte vad som gör det till funtionell programmering. Det är ett helt annat tänk. Det finns exempelvis bara funktioner, inga variabler. Googla lite exempel. Det är väldigt svårt att summera "skillnaden", då det är typ helt annorlunda.

Visa signatur

Core i7 7700K | Titan X (Pascal) | MSI 270I Gaming Pro Carbon | 32 GiB Corsair Vengeance LPX @3000MHz | Samsung 960 EVO 1TB

Permalänk
Inaktiv

Funktionell programmering:

https://sv.wikipedia.org/wiki/Funktionell_programmering

Många moderna språk har anammat delar av funktionell programmering. Kotlin är ett exempel. Ren funktionell programmering är mest av akademiskt intresse. Man skall ha lite koll på de begrepp som verkligen används, som då varierar från språk till språk, men i övrig kan man ta det rätt lugnt.

Permalänk
Medlem

Ja men då kan vi bortse från mina svar i tråden

Permalänk
Hedersmedlem

En annan uppdelning är att skilja på funktionella (där funktioner helst endast använder inparametrar för att generera returvärdet och inte har bieffekter) språk och imperativa språk (där kommandon i en viss ordning påverkar tillståndet hos programmet (så två anrop till samma funktion med samma parametrar kan eventuellt ge olika resultat)).

Permalänk
Medlem

Om nån kommer in här och inte fattar alls, ett exempel på funktionell programmering är följande, en funktion som tar en träddatastruktur och ger tillbaka en helt ny där en funktion har tillämpats på alla element i ursprungliga trädet. D v s man ska absolut inte modifiera det ursprungliga trädet, och allting görs genom att mapfunktionen ropar på sig själv tills den når yttersta delarna på trädet istället för via loopar.

Permalänk
Medlem
Skrivet av ronnylov:

Programspråk som C är enbart funktionell programmering.

Du verkar lite förvirrad här. C anses inte vara ett funktionellt programmeringsspråk.
Det är ett procedurellt, imperativt programmeringsspråk.

Att subrutiner i C (och efterföljare som t.ex Java) kallas "funktioner" är bara för att de kan ge ett värde som svar. (i C: ett värde eller "void".)
I vissa andra procedurella språk som t.ex. Pascal så har man en uppdelning mellan de som ger svar ("function") och de som inte gör det ("procedure").

I ett funktionellt språk så består en funktion bara av ett uttryck (som visserligen kan vara komplext och innehålla konditionella deluttryck), medans i ett imperativt språk så består en subrutin av flera satser efter varandra.
I ett rent funktionellt språk ("pure functional") så kan en funktion bara läsa sina inparametrar och bara ge ett svar: inte påverka eller påverkas av världen på annat sätt. (kallas också: inte ha sidoeffekter)

Men visst kan man skriva program i en funktionell stil i många procedurella språk, precis som man t.ex. kan skriva objekt-orienterad C (om än omständigt) eller procedurell C++ (använda det som "ett bättre C").
Vissa språk som bl.a. Scala kallas för "multiparadigm-språk" därför att de är utformade för programmering i flera stilar.

Visa signatur

För övrigt anser jag att tobak ska förbjudas.

Permalänk
99:e percentilen

För att lyckas med funktionell programmering behöver du bryta dig loss ur tankesättet "Hur?" och istället tänka "Vad?". Istället för att tänka i sidoeffekter behöver du tänka i värden.

I ett imperativt språk tänker man kanske ofta:

"Jag vill loopa igenom den här listan och göra något med varje element."

Och så skriver man en for-loop:

for (const x of xs) { doSomething(x); }

I ett sådant exempel kommer doSomething absolut att ha sidoeffekter, annars vore det helt meningslöst att skriva loopen överhuvudtaget.

I en helt funktionell kontext (se mitt inlägg ovan) kan man inte tänka så, utan då måste man tänka:

"Jag vill beskriva resultatet av att applicera en funktion på varje element i den här listan."

Detta kallas att mappa en funktion över en lista och är ett typexempel på funktionell programmering. Här handlar det inte om att "beordra datorn att göra saker", utan att "berätta för den vad saker är". I Haskell skulle man kunna göra så:

mapMyFunction [] = [] mapMyFunction (x:xs) = something x : mapMyFunction xs

I detta exempel är something en funktion (garanterat ren eftersom Haskell bara har rena funktioner) som returnerar ett värde. Vi berättar först att mapMyFunction av den tomma listan är den tomma listan. Sedan berättar vi att mapMyFunction av en lista som börjar med x och fortsätter med xs är listan som börjar med something x och fortsätter med mapMyFunction xs.

(Notera alltså att funktioner i Haskell är funktioner i matematisk bemärkelse: Det enda de kan "göra" är att beräkna ett returvärde, och det returvärdet är alltid samma varje gång funktionen anropas med samma argument: f x är alltid lika med f x för alla f och x, hur många gånger man än evaluerar det. Det finns till exempel ingen motsvarighet till JavaScripts Math.random i Haskell; det går knappt att komma längre från matematiska funktioner än så.)

I verkligheten skulle man inte skriva mapMyFunction, utan man skulle använda den mer generella map ur standardbiblioteket, som på ett mycket bra sätt åskådliggör även den andra aspekten av funktionell programmering, nämligen att funktioner är värden. map kan definieras så här:

map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = f x : map f xs

Först berättar vi att resultatet av att mappa vilken funktion som helst över den tomma listan är den tomma listan. Sedan berättar vi att resultatet av att mappa funktionen f över listan som börjar med x och fortsätter med xs är listan som börjar med f x och fortsätter med map f xs.

(Den första raden är typsignaturen för map, som talar om att funktionen som mappas måste kunna ta in som argument element av den typ (a) som finns i listan som mappas över, och att resultatet kommer vara en lista av den typ (b) som funktionen som mappas returnerar.)

I JavaScript, som inte lämpar sig så väl för den helt funktionella stil som är idiomatisk i Haskell, skulle map kunna skrivas så här:

function map(f, xs) { const ys = []; for (const x of xs) { ys.push(f(x)); } return ys; }

I verkligheten skulle man dock använda Array.prototype.map:

const ys = xs.map(f);


Det jag tycker är viktigt är att inse att man har otroligt stor nytta av funktionell programmering även i imperativa språk. Man behöver inte ens – och bör ofta inte – skriva rent funktionella implementationer, men man kan ändå skriva rena funktioner, som absolut kan använda imperativa implementationsdetaljer för att komma fram till sina returvärden (se JavaScript-exemplet ovan!). Här är ett bra exempel på hur jag brukar skriva rena funktioner i TypeScript.

"Varför ska man använda funktionell programmering?"

För att sidoeffekter är ruskigt svåra att arbeta med, särskilt när kodbaser växer. En ren funktion är mycket lättare att tänka på i isolering, refaktorera, flytta, testa osv, än kod som modifierar och/eller beror på (mer eller mindre) globalt tillstånd hit och dit.

"Men arbetsmarknaden använder inte Haskell; varför ska jag?"

Det ska du nog inte, mer än kanske för att lära dig funktionell programmering, vartill Haskell är alldeles utmärkt. Min förhoppning är endast att fler ska få upp ögonen för att dra nytta av funktionella principer i imperativa språk som JavaScript, Java, Python osv. Jag upplever själv att min egen kod blivit otroligt mycket lättare att förstå och hantera sedan jag gick över från i stort sett enbart sidoeffekter till i hög grad värden som den grundläggande byggstenen i mina program.

Förtydligade första Haskell-exemplet.
Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem
Skrivet av Xenofonus:

Om nån kommer in här och inte fattar alls, ett exempel på funktionell programmering är följande, en funktion som tar en träddatastruktur och ger tillbaka en helt ny där en funktion har tillämpats på alla element i ursprungliga trädet. D v s man ska absolut inte modifiera det ursprungliga trädet, och allting görs genom att mapfunktionen ropar på sig själv tills den når yttersta delarna på trädet istället för via loopar.

https://i.imgur.com/wyG0ipG.png
https://i.imgur.com/dJ6Hzma.png

Läste funktionell programmering i Erlang på KTH, och efter den kursen har jag jag aldrig stött på något funktionellt språk igen. Jag jobbar nu som systemutvecklare. Och ja, det blev lite lättare när man hade kommit in i det, men det är ett rätt skevt tänk..

En databas som ajg skrev i erlang som jag är smått stolt över hur kort koden blev:

-module (db). -export ([new/0, destroy/1, write/3, read/2, delete/2, match/2, append/2, matchRet/2]). new() -> []. destroy(_DbRef) -> ok. write(Key, Element, []) -> [{Key, Element, [], []}]; % Skapa första elementet, eller där det inte finns något write(Key, Element, [{DbKey, DbElement, Lhs, Rhs}]) when Key > DbKey-> [{DbKey, DbElement, Lhs, write(Key, Element, Rhs)}]; % Nyckeln på det nya elementet är större, gå ner i högerled write(Key, Element, [{DbKey, DbElement, Lhs, Rhs}]) when Key < DbKey-> [{DbKey, DbElement, write(Key, Element, Lhs), Rhs}]; % Nyckeln på det nya elementet är större, gå ner i högerled write(Key, Element, [{Key, _, Lhs, Rhs}]) -> [{Key, Element, Lhs, Rhs}]. % Nyckeln matchar ett redan existerande, ersätt elementet. read(Key, [{DbKey, _, _, Rhs}]) when Key > DbKey -> read(Key, Rhs); % Leta efter elemtenet som ska returneras, nyckeln är större än nuvarande, gå ner i höger read(Key, [{DbKey, _, Lhs, _}]) when Key < DbKey -> read(Key, Lhs); % Leta efter elemtenet som ska returneras, nyckeln är mindre än nuvarande, gå ner i vänster read(Key, [{Key, DbElement, _, _}]) -> {ok, DbElement}; % Nyckeln matchar, returnera elementet. read(_Key, []) -> {error, instance}. % Ingen nyckel matchade. match(Element, [{DbKey, Element, Lhs, Rhs}]) -> matchRet({ok, DbKey}, matchRet(match(Element, Lhs),match(Element, Rhs))); % Hittade ett matchande element, men fortsätter och letar ifall det finns fler. match(Element, [{_, _, Lhs, Rhs}]) -> matchRet(match(Element, Lhs), match(Element, Rhs)); % Elementet är inte rätt, kolla i båda grenarna match(_Element, []) -> {error, instance}. % Hittade inget element. matchRet({ok, Val}, {error, instance}) -> {ok, Val}; % Return value if not both is error matchRet({error, instance}, {ok, Val}) -> {ok, Val}; % Return value if not both is error matchRet({ok, LhsVal}, {ok, RhsVal}) -> {ok, [LhsVal] ++ [RhsVal]}; % Return value if not both is error matchRet({error, instance}, {error, instance}) -> {error, instance}. %return error delete(Key, [{DbKey, DbElement, Lhs, Rhs}]) when Key > DbKey-> [{DbKey, DbElement, Lhs, delete(Key, Rhs)}]; %L eta efter elemtenet som ska tas bort, nyckeln är större än nuvarande, gå ner i höger delete(Key, [{DbKey, DbElement, Lhs, Rhs}]) when Key < DbKey-> [{DbKey, DbElement, delete(Key, Lhs), Rhs}]; % Leta efter elemtenet som ska tas bort, nyckeln är mindre än nuvarande, gå ner i vänster delete(Key, [{Key, _, Lhs, Rhs}]) -> append(Lhs, Rhs); % Ersätt nuvarande element med den i Rhs, och placera Lhs i första tomma Lhs plats i Rhs delete(_Key, []) -> []. append(OriginalLhs, [{DbKey, DbElement, [], Rhs}])-> [{DbKey, DbElement, OriginalLhs, Rhs}]; % If Lhs == null, lägg in Original Lhs append(OriginalLhs, []) -> OriginalLhs; append(OriginalLhs, [{DbKey, DbElement, Lhs, Rhs}])-> [{DbKey, DbElement, append(OriginalLhs, Lhs), Rhs}]; %If Lhs != null, Gå ner i Lhs och leta en ledig Lhs append([],[])->[]. % Om ett löv togs bort

Dold text
Visa signatur

CPU: Ryzen 9 3900x Noctua NH-D14 MOBO: TUF Gaming X570-PLUS GPU: GTX 980 RAM: 32 GB 3200 MHz Chassi: R4 PSU: Corsair AX860 Hörlurar: SteelSeries 840 Mus: Logitech G502 Lightspeed V.v. nämn eller citera mig för att få svar.

Permalänk
Medlem

EDIT: Vet inte om det jag skrev stämde 100%, återkommer eventuellt.

Visa signatur

RAID is not a backup

Permalänk
Medlem

Jag kommer ihåf när jag satt med Haskell och tidigare kom från et dominerat OO tänk. Jag greppade det rätt fort men satt och frågade mig själv "varför detta?, varför såhär?" osv...

Men nåonstans halvvägs genom kursen så tyckte jag det var jätteskönt och lösningar kunde man se i huvudet på ett funktionellt sätt precis som man tidigare kunnat med OO. Skrev lösningar för att räkna ut vägar och tider genom spårvagnsnät och en massa annat. Vissa saker kändes mycket enklar eoch smidigare i Haskell och även om jag idag igen sitter mestadels med OO så skulle jag säga att jag tog med mig mycket och upptäckte saker jag inte visste att jag saknade i OO.

C++ kan man ju faktiskt koda i en massa olika paradigmer i, men det är kanske lite krångligare än ett språk som är skrivet med funktionell programmering i huvudet.

Visa signatur

Huvudriggen är en Gigabyte Aorus Xtreme | 128gb DDR5 6000 | Ryzen 7950X | 3080Ti
Utöver det är det för många datorer, boxar och servar för att lista :P

Permalänk
99:e percentilen
Skrivet av Xenofonus:

Blir det här tänket som skiljer sig så mycket från OOP någonsin lättare att resonera kring?

Ja. Tröskeln är hög; det minns jag själv mycket väl från när jag plötsligt, i första kursen på högskolan, skulle skriva Haskell efter flera år med JavaScript, där jag hade missbrukat sidoeffekter in absurdum.

Det är när du kan sluta tänka på vad koden ska göra och börja tänka på hur du kan räkna ut värden baserat på andra värden som du kan internalisera det funktionella tänket.

Citat:

Hur mycket stöter man på FP ute i riktiga världen?

Rent funktionella språk: knappt alls.
Funktionella principer: i så gott som alla välskrivna kodbaser.

Visa signatur

Skrivet med hjälp av Better SweClockers

Permalänk
Medlem

@Xenofonus: Jag skulle lära mig ett "rent" funktionellt språk innan jag går på paradigm blandningar som Scala & Kotlin då det kan vara svårt att ordentligt förstå de funktionella bitarna om man kommer från en OOP bakgrund.

Jag skulle rekommendera att du lär dig en Lisp dialekt och enligt mig så är Racket smidigast och har mycket bra material som denna bok: How to Design Programs.

Sedan när du känner dig bekväm med Racket så kan du gå vidare till Clojure eller Kotlin/Scala.

Rent praktiskt i arbetslivet så är det sällan man stöter på ren funktionell programmering utan det är mest enstaka moduler eller paradigmblandingar som Scala eller att man skriver koden på ett funktionellt sätt. Det finns visserligen områden och brancher där det är mer vanligt och jag tror det kan bli mer vanligt då Microservices är nu en grej och funktionell programmering passar väldigt bra till det konceptet.

Visa signatur

3900X, RX 6700 XT, 32gb RGB RAM.

Permalänk
Medlem

XSLT får nog betraktas som ett i väldigt hög grad funktionellt språk. Det är tydligen turing-komplett. Det är det enda språk jag använt i arbetslivet för icke-triviala uppgifter där det inte finns ett imperativt angreppssätt tillgängligt i språket.

För er som inte vet vad XSLT är: Det används nästan uteslutande för att transformera XML och likartad markup (som HTML) till ett annat format i samma stil. XSLT 3.0 ska tydligen även kunna hantera JSON.

Microsoft-varianten jag använde hade tillägg för att anropa valfri C#, så det gick rätt bra att fuska till diverse sidoeffekter...

Permalänk
Medlem

Det går också att göra vettig kod i (nästan) vilket språk som helst, ofta vill man låna in funktionella koncept menar jag ; om man använder funktionella koncept så underlättare det om både språket och kompilatorn kan hjälpa till

( och jag hoppas personligen att språk med funktionella tendenser växer sig starkare tex Rust är ett av många språk som är delvis funktionellt som jag personligen börjat kika på.
Om inte annat så tillkommer funktionella aspekter inom etablerade språk som tex labmda/maybe/linq som tillkommer till etablerade språk som c++/Java/C# ).

tillägg rekomendation:
jag vill precis som tidigare inlägg rekomendera att du försöker lära dig funktionell programmering, helst genom att gå "all in" i något mera pure funktionellt språk, spelar mindre roll vilket språk det rör sig om ...
Det är alltför enkelt att falla tillbaka på gammal erfarenhet och koda imperativt även om man tekniskt sätt använder ett funktionellt språk.

Min rekomendation är att, om ni har tid och vill bli en bra kodare, då bör funktionell programmering vara en av sakerna högst upp på kompetenslistan.

  • Det är dock något som måste få ta lite tid att lära sig

  • i början så kommer ni i princip lätt att bli en "sämre" kodare temporärt innan de nya tankesättet "klickar" i huvudet

  • men det viktiga är att inte ge upp och att inte tillåta sig själv att koda på "gamla sätt" under inlärningsperioden.

  • ge inte upp, det är extremt viktiga koncept och väl värt tiden det (tyvär) kan ta att lära sig.

  • Det är inte kritiskt att lära sig alla funktionella koncepten, de enklare delarna är viktiga, men mera avancerade delar är inte alls lika viktiga (monader etc skulle jag personligen tex inte försöka lära mig om jag inte redan indirekt känner till lite grann om dem ... som tex promises, linq, maybe etc är ofta tekniskt sätt monader)

När ni sedan går tillbaka till normalt kodande då behöver ni inte vara låsta vid att alltid använda funktionell programmering .... det enda jag starkt rekomenderar är att ni tvinger er själva att _bara_ göra funktionell programmering under ev inlärnings-tiden om ni väljer att försöka lära er funktionell programmering.

tilllägg rekomendaton och stavning
Permalänk
Datavetare

Som flera redan skrivit i tråden: att lära sig att programmera rent funktionellt är väldigt nyttigt då de flesta av de "stora" språken idag har stöd för att använda högre ordningens funktioner och liknande.

I "verkligheten" här det inte på något sätt negativt att använda imperativa språk med "mutable state". CPUer har optimerats för att så effektivt så möjligt jobba med "mutable state" och i grunden är alla välanvända CPU-arkitekturer imperativa. "Mutable state" är i sig bara ett problem om samtidigt delas av mer ett kontext.

Är just den insikten Rust bygger på. Initialt hade Rust saker som "pure functions", men de försvann rätt snabbt då målet var att skapa ett modernt systemspråk med hårdfokus på att kunna skriva högpresterande applikationer. Funktionell programmering fungerar bra i det lilla, i praktiken ungefär så länge som man kan hålla de funktionella bitarna i register eller i värsta fall som automatiska variabler ("stacken"). Därför är det vettigt att tvinga sig lära något rent funktionellt språk, det är väldigt användbart som bas i moderna program just då de flesta "stora" språk stödjer detta och det finns massor med fördelar.

Tidigare hade man rätt stora förhoppningar att funktionella språk skulle "automagiskt" lösa de stora utmaningarna att skriva korrekta multitrådade program. I teorin fungerar det lysande, i praktiken fungerar det långt sämre och den insikt man kommit till är att gå till rent funktionell programmering är att gå längre än vad man behöver.

"Mutable state" är faktiskt rätt enkelt och logiskt att jobba med förutsatt att man inte blandar in "magiska" saker (global state) samt endera säkerställer att ingen ändrar ett delat tillstånd alt. att det för tillfället bara finns max en tråd som kan skriva till en potentiellt delat tillstånd. Är ju exakt detta Rust såg till att hantera redan vid kompilering. Rust är inte ett funktionellt språk, det är ett imperativt språk som garanterar att deltat tillstånd endera är "read only" eller att maximalt en tråd har en referens till data (blir kompileringsfel om det är möjglit för fler än ett kontext att referera "mutable" data samtidigt).

Rent funktionella språk ger samma garanti som Rust, men då de inte tillåter "mutable state" blir prestanda rätt lidande då saker trots allt måste köra på de CPUer vi har. Och de har som sagt optimerats för "mutable state" och imperativa program under väldigt lång tid.

En annan sak man ska ta med sig från funktionella språk är att ha en hård separering av data (dina "värden") och sätt att transformera data (dina funktioner). Just den delen är vad OOP fått totalt fel: enda sättet att effektivt och korrekt hantera data i multicore miljöer är att låta data vara synligt och explicit, inte inkapslat!

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

@Yoshman: Tackar så mycket för ett fint svar. Ja Rust låter verkligen som ett otroligt väldesignat språk, jag har det överst på listan över saker jag vill prova framöver när jag har tid att sätta in mig i ett lägre nivå språk.

Permalänk
Medlem
Skrivet av Xenofonus:

Blir det här tänket som skiljer sig så mycket från OOP någonsin lättare att resonera kring?
Hur mycket stöter man på FP ute i riktiga världen?

Det långa svaret: https://youtu.be/6YbK8o9rZfI

(Notera: Talaren är en känd förespråkare för det funktionella språket Elm som han använder i jobbet.)

Permalänk
Medlem
Skrivet av Alling:

För att lyckas med funktionell programmering behöver du bryta dig loss ur tankesättet "Hur?" och istället tänka "Vad?". Istället för att tänka i sidoeffekter behöver du tänka i värden.

I ett imperativt språk tänker man kanske ofta:

"Nu ska jag loopa igenom den här listan och göra något med varje element."

Och så skriver man en for-loop:

for (const x of xs) { doSomething(x); }

I ett sådant exempel kommer doSomething absolut att ha sidoeffekter, annars vore det helt meningslöst att skriva loopen överhuvudtaget.

I en helt funktionell kontext (se mitt inlägg ovan) kan man inte tänka så, utan då måste man tänka:

"Jag vill beskriva resultatet av att applicera en funktion på varje element i den här listan."

Detta kallas att mappa en funktion över en lista och är ett typexempel på funktionell programmering. Här handlar det inte om att "beordra datorn att göra saker", utan att "berätta för den vad saker är". I Haskell skulle man kunna göra så:

mapSomething [] = [] mapSomething (x:xs) = something x : mapSomething xs

I detta exempel är something en funktion (garanterat ren eftersom Haskell bara har rena funktioner) som returnerar ett värde. Vi berättar först att mapSomething av den tomma listan är den tomma listan. Sedan berättar vi att mapSomething av en lista som börjar med x och fortsätter med xs är listan som börjar med something x och fortsätter med mapSomething xs.

(Notera alltså att funktioner i Haskell är funktioner i matematisk bemärkelse: Det enda de kan "göra" är att beräkna ett returvärde, och det returvärdet är alltid samma varje gång funktionen anropas med samma argument: f x är alltid lika med f x för alla f och x, hur många gånger man än evaluerar det. Det finns till exempel ingen motsvarighet till JavaScripts Math.random i Haskell; det går knappt att komma längre från matematiska funktioner än så.)

I verkligheten skulle man inte skriva en sådan funktion, utan man skulle använda den mer generella map ur standardbiblioteket, som på ett mycket bra sätt åskådliggör även den andra aspekten av funktionell programmering, nämligen att funktioner är värden. map kan definieras så här:

map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = f x : map f xs

Först berättar vi att resultatet av att mappa vilken funktion som helst över den tomma listan är den tomma listan. Sedan berättar vi att resultatet av att mappa funktionen f över listan som börjar med x och fortsätter med xs är listan som börjar med f x och fortsätter med map f xs.

(Den första raden är typsignaturen för map, som talar om att funktionen som mappas måste kunna ta in som argument element av den typ (a) som finns i listan som mappas över, och att resultatet kommer vara en lista av den typ (b) som funktionen som mappas returnerar.)

I JavaScript, som inte lämpar sig så väl för den helt funktionella stil som är idiomatisk i Haskell, skulle map kunna skrivas så här:

function map(f, xs) { const ys = []; for (const x of xs) { ys.push(f(x)); } return ys; }

I verkligheten skulle man dock använda Array.prototype.map:

const ys = xs.map(f);


Det jag tycker är viktigt är att inse att man har otroligt stor nytta av funktionell programmering även i imperativa språk. Man behöver inte ens – och bör ofta inte – skriva rent funktionella implementationer, men man kan ändå skriva rena funktioner, som absolut kan använda imperativa implementationsdetaljer för att komma fram till sina returvärden (se JavaScript-exemplet ovan!). Här är ett bra exempel på hur jag brukar skriva rena funktioner i TypeScript.

"Varför ska man använda funktionell programmering?"

För att sidoeffekter är ruskigt svåra att arbeta med, särskilt när kodbaser växer. En ren funktion är mycket lättare att tänka på i isolering, refaktorera, flytta, testa osv, än kod som modifierar och/eller beror på (mer eller mindre) globalt tillstånd hit och dit.

"Men arbetsmarknaden använder inte Haskell; varför ska jag?"

Det ska du nog inte, mer än kanske för att lära dig funktionell programmering, vartill Haskell är alldeles utmärkt. Min förhoppning är endast att fler ska få upp ögonen för att dra nytta av funktionella principer i imperativa språk som JavaScript, Java, Python osv. Jag upplever själv att min egen kod blivit otroligt mycket lättare att förstå och hantera sedan jag gick över från i stort sett enbart sidoeffekter till i hög grad värden som den grundläggande byggstenen i mina program.

Något man bör tillägga är att "funktioner" i programmeringssammanhang kan nästan vara vad som helst: ett antal print-satser, en algoritm, en procedur, ett visst arbete osv. "Funktionell programmering" förutsätter att funktioner i det sammanhanget lyder vissa lagar och regler: referential transparency som Alling förklarar med att f x returnerar samma output så länge input x är oförändrat; en funktion tar alltid in värden, input, och returnerar ett annat värde, output. Vissa programmeringsspråk är beroende av sidoeffekter av den enkla anledningen att de arbetar med call-by-value och call-by-reference. Man kan t.ex. inte göra något så enkelt som att byta plats på två värden i C utan att använda sig av sidoeffekter och då blir retur-typen Void.

#include <stdio.h> void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } int main(void) { int x = 7; int y = 42; swap(&x, &y); printf("%d, %d\n", x, y); return 0; }

Visa signatur

| Ryzen 3800XT | Corsair LPX 32GB | B550 Aorus Elite v2 | GTX 970 | Samsung 970 Evo | CM Masterbox 520 | RM750x | Windows 7

| Ryzen 2700 | Corsair LPX 16GB | Prime B450-Plus | GT 1030 | Samsung 970 Evo | Kolink Observatory | RM750x | EndeavourOS

| JDS Labs Atom | Khadas Tone Board | Fostex TX-X00 & HIFIMAN HE4XX |

Permalänk
Hedersmedlem
Skrivet av futhark14:

Man kan t.ex. inte göra något så enkelt som att byta plats på två värden i C utan att använda sig av sidoeffekter och då blir retur-typen Void.

Hela idén med att skicka in två parametrar i en funktion som ändrar dem känns väl lite icke-funktionell? Det vanliga är väl att skicka in dem (”by value”) och få tillbaka två nya (som har bytt plats) och det borde man kunna göra även i C. C tillåter ju dock att man gör på många andra sätt också, men man påstår sig ju inte heller vara ett funktionellt språk.

Permalänk
Medlem

Har ju inte egentligen med funktionell programmering att göra utan snarare att man väljer att jobba med immutability

Permalänk
Medlem
Skrivet av Elgot:

Hela idén med att skicka in två parametrar i en funktion som ändrar dem känns väl lite icke-funktionell? Det vanliga är väl att skicka in dem (”by value”) och få tillbaka två nya (som har bytt plats) och det borde man kunna göra även i C. C tillåter ju dock att man gör på många andra sätt också, men man påstår sig ju inte heller vara ett funktionellt språk.

Det jag syftade på var att man är tvungen att använda pekarsemantik i C.
Om man t.ex. utför en liknande beräkning i Haskell så tror jag att det inte ens används call-by-value eller call-by-reference ty Haskell är lazy och evaluerar saker till weak head normal form(2+3) så länge normal form(5) inte behövs i en beräkning.

test :: Int -> Int -> (Int, Int) test x y = swap (x*3, y*3) swap :: (Int, Int) -> (Int, Int) swap (a,b) = (b,a) swap2 :: (Int, Int) -> (Int, Int) swap2 (a,b) | a > b = (a, b) | b > a = (b, a)

Innan man anropar swap så multiplicerar man talen med tre och jag tror att dessa aldrig beräknas till normal form i swap på grund av att man inte behöver dessa värden men de beräknas till normal form i swap2 på grund av att dessa värden används.

Visa signatur

| Ryzen 3800XT | Corsair LPX 32GB | B550 Aorus Elite v2 | GTX 970 | Samsung 970 Evo | CM Masterbox 520 | RM750x | Windows 7

| Ryzen 2700 | Corsair LPX 16GB | Prime B450-Plus | GT 1030 | Samsung 970 Evo | Kolink Observatory | RM750x | EndeavourOS

| JDS Labs Atom | Khadas Tone Board | Fostex TX-X00 & HIFIMAN HE4XX |

Permalänk
Hedersmedlem
Skrivet av futhark14:

Det jag syftade på var att man är tvungen att använda pekarsemantik i C.

Fast om haskell-versionen tillåts ta konstanta inparametrar och returnera nya värden borde det väl vara tillåtet i C också? Nu är man ju lite begränsad till endast ett returvärde, men man skulle väl kunna tänka sig en struct både in och ut?

Edit:
Typ såhär:

typedef struct { int x, y; } XY; XY swap(XY in) { return (XY) {.x = in.y, .y = in.x}; } int main(int argc, char** argv) { XY a = {.x = 5, .y = 10}; a = swap(a); return 0; }

Permalänk
Medlem

Jag tycker F# är trevligt. Men sen är jag ju hjärntvättad av arbetsplatsen och så vidare...

Men det lustiga är att jag har svårt för OOP istället, vilket kanske inte är så vanligt.

Jobb finns det dock, men verkligen inte lika mycket

Visa signatur

Grubblare

Permalänk
Medlem

tldr lär dig Haskell