Etiska perspektiv på programmering

Det var länge sedan jag skrev något nu, så jag tänkte att det är dags 🙂

Skolverket har i samband med representanter från flera lärosäten arbetat fram ramkursplaner för introduktionskurser i visuell respektive textbaserad programmering. I dessa ramkursplaner finns följande formulering med under rubriken ”Generellt innehåll”:

Etiska perspektiv på programmering

Eftersom jag själv tycker att det här med etiska aspekter på datavetenskap, IT och programmering är jätteintressant så var min första tanke ”Ja!”. Sen kom min andra tanke: ”Men, vad menar de egentligen?”

Jag ämnar försöka fördjupa mig i vad de egentligen menar, men det blir antagligen i ett annat inlägg. Här tänkte jag istället resonera kring vad etiska perspektiv på programmering skulle kunna vara och hur etiska ställningstaganden är högst relevanta i en programmeringskontext.

Vad var programmering nu igen?

Låt mig först rekapitulera min beskrivning av vad programmering är: en problemlösningsprocess där man går från problem till en lösning på problemet som inbegriper en programmerbar enhet, exempelvis en dator. Vägen från problem till lösning inbegriper bland annat problemformulering, formalisering, en lösningsstrategi, att formulera strategin som en algoritm, att koda algoritmen samt att testa och utvärdera sin lösning.

Med denna definition av programmering, som en process snarare än enbart kodningsdelen av hela problemlösningsprocessen, är det många val, ställningstaganden och förutsättningar som görs längs vägen. En del av dem är explicita, men många av dem är också implicita.

Och här är ”grejen”, själva huvudbudskapet i detta inlägg:

De konsekvenser som ett system får för de som använder systemet är summan av de val och antaganden som har gjorts under designprocessen. 

Vad menar jag då med detta?

Jo, att för alla tekniska system som har konstruerats av någon har det funnits en problemlösningsprocess där man i varje steg gör val och antaganden som påverkar hur det slutliga systemet kommer att fungera (och i många fall, inte fungera under vissa omständigheter).

I en tid då man i allt större utsträckning använder sig av algoritmer för att styra och kontrollera är det viktigt att algoritmerna faktiskt fungerar, gör sitt jobb som de ska och framför allt inte påverkar tredje part negativt. Det finns många exempel på att detta inte alltid fungerar så bra alla gånger och där det ibland kan få förödande konsekvenser för enskilda individer, familjer, samhällsgrupper med mera.

Exempel 1: En app för att hålla reda på menstruationscykler som påstods vara lika tillförlitlig som ett fysiskt preventivmedel. Gissa vad? Det var den inte. Vilket ledde till ett antal oönskade graviditeter som i sin tur behövde hanteras på något sätt. Eftersom detta hände i USA resulterade det givetvis även i att företaget bakom appen stämdes.

Exempel 2: ”Leendedetektor” i kamera som känner av när en person i sökaren ler och tar en bild i rätt ögonblick. Problemet här var att algoritmen inte fungerade för alla hudfärger och etniciteter; specifikt hade asiatiska personer svårt att få den att fungera.

Exempel 3: Automatisk tvåldispenser som inte fungerar på mörkhyade personer. Om någon med för mörk hudfärg vill ha tvål måste de vifta med ett vitt papper under sensorn för att få ut tvål ur dispensern.

Det är bara några exempel. Det finns många andra exempel på system där man inte har tänkt på allt som kan inträffa, eller på alla de egenskaper som olika användare av det färdiga systemet kan ha. Inom de branscher som utvecklar olika typer av tekniska system och mjukvara finns det metoder för att förebygga dessa typer av problem, men det är inte alltid de används fullt ut av olika anledningar.

De exempel jag har nämnt ovan handlar huvudsakligen om misstag som har gjorts i designprocessen; det som jag tidigare benämnde som implicita val och antaganden. Utöver dessa finns det också explicita antaganden som görs. Dessa kan baseras på ett företags policy, personliga uppfattningar hos personer i designteamet, ideologiska ställningstaganden m.m.

Ett sunt förhållningssätt till ett tekniskt system kan vara att reflektera över vilka intressenter som finns för systemet. Detta gäller särskilt om systemet är gratis; det är väldigt ont om altruistiska personer som bara vill väl – är det gratis så finns det någon vars behov uppfylls av att du använder systemet. Frågan är då vad det behovet är och om du vill ställa upp på att det uppfylls?

Ett klassiskt exempel på det sistnämnda är Google som erbjuder ett antal väldigt användbara tjänster helt gratis. Priset du betalar är att de samlar in data om dina beteenden som sedan kan analyserar och säljas vidare till de som är intresserade av vem du är, kanske i syfte att erbjuda riktad reklam eller avgöra om du är mottaglig för en viss typ av propaganda.

Låter det orimligt? Har du hört talas om Cambridge Analytica? I korthet såldes stora mängder insamlad personlig information till företaget som sedan använde det för att rikta politisk reklam till personer som bedömdes vara mottagliga för att rösta på en viss kandidat.

Insamlad data som analyseras, gratisappar med en inte alltid tydlig agenda, system som innebär negativa konsekvenser för vissa användare – de är alla olika konsekvenser av samma faktum:

De konsekvenser som ett system får för de som använder systemet är summan av de val och antaganden som har gjorts under designprocessen. 

Hur kan man då medvetandegöra nybörjare på programmering om detta? Det finns flera sätt.

Ett sätt kan vara att helt enkelt studera ett befintligt system och ställa kritiska frågor. Vem tillhandahåller systemet? Vad har denne för egenintresse i att systemet finns? Vad händer om systemet inte längre fungerar? Vad finns det för alternativa lösningar? Kan detta system användas av alla? Om inte, vilka är exkluderade? Är det rimligt att dessa är exkluderade? Ett system som är ganska tacksamt att studera på detta sätt är Bank-ID med hänvisning till det genomslag det har fått.

Ett annat sätt kan vara att dela ut en designuppgift där man ska skissa på ett system som löser ett givet problem och reflektera över vilka antaganden man behöver göra för att få ihop en design, samt vad konsekvenserna av dessa antaganden är. Om jag förutsätter att användaren har tillgång till en pekskärm, vad är konsekvenserna av det? Om jag förutsätter att användaren alltid är uppkopplad, vad får det för effekter? Och så vidare.

Etiska perspektiv på programmering

Vad har då allt detta att göra med etiska perspektiv på programmering?

Allt.

Kom ihåg:

De konsekvenser som ett system får för de som använder systemet är summan av de val och antaganden som har gjorts under designprocessen. 

Den eller de personer som gör dessa val och antaganden skapar konsekvenser som kan drabba tredje part. Vem är det egentligen som har ansvaret? Vad är det rimligt att man som programmerare förutse och var går gränsen mellan något som borde ha kunnat förutses av programmeraren, beställaren, användaren?

I ett samtida samhälle där IT finns överallt och vi i allt större utsträckning använder algoritmer som beslutsunderlag gäller det att som programmerare ha en tydlig moralisk kompass, vara medveten om potentiella konsekvenser av sina val och antaganden och ha ett rimligt förhållningssätt till ansvarsfördelningen.

Ett avslutande exempel

Jag avslutar med ett illustrerande exempel från min hemkommun, Uppsala. I det fria skolvalet fördelas barn mellan olika skolor baserat på begreppet relativ närhet som implementeras av en algoritm. Denna algoritm samkör familjernas önskemål med kartdata och försöker jämka fram något som blir så bra som möjligt för så många som möjligt. Vid närmare granskning har det visat sig att systemet som används har en del tveksamheter.

En tveksamhet är att vägen till skolan beräknas med en algoritm som tycker det är rimligt att 10-åriga barn skall cykla genom en hårt trafikerad rondell istället för att använda den cykeltunnel som finns precis bredvid. En annan tveksamhet är att avståndet mellan hem och skola mäts på meternivå, vilket innebär att ett barn kan förlora den mest realistiska skolplaceringen därför att det bor 3 meter för långt bort från skolan. Ytterligare en tveksamhet är att avståndet mellan hem och skola mäts från tomtgräns till tomtgräns.

Dessa tveksamheter sammantaget ger att algoritmen inte fungerar bra för alla. En del drabbas hårt. Och i detta fall var det endast de som var medvetna om teknikens roll i det hela som protesterade och så småningom fick rätt.

Vem bär ansvaret för att det blev fel? Företaget som använde kartdata på ett tveksamt sätt i sitt system? Kommunen som inte verifierade rimligheten i de bedömningar som algoritmen gjorde innan systemet driftsattes? Skolkontoret som kategoriskt hänvisade till att det är rättvist eftersom samma icke-perfekta algoritm används för alla bedömningar? Vem ska ställas till svars för att hela familjer drabbas av ett system som inte är helt perfekt?

En sak är i alla fall säker:

Etiska perspektiv på programmering är ett brett ämne som kan angripas från många intressanta vinklar. Jag ska försöka bena ut vad Skolverket egentligen har tänkt sig i ett senare inlägg.

 

Kreativt användande av binära tal

Tidigare i veckan anordnade jag och Linda Mannila en workshop i ämnesöverskridande digital kreativitet för en grupp grundskolelärare. I samband med detta dök det upp en problemställning kring programmering i Scratch som var ny för mig. Eller inte ny egentligen, men jag hade aldrig gjort något liknande i Scratch. Sedan när jag funderade lite mer på det så insåg jag att det handlade om en tillämpning av hur binära tal fungerar i ett högst påtagligt problemscenario.

Vi hade en grupp som ville göra en interaktiv poster där det fanns flera knappar för olika grundämnen. Genom att trycka ner olika kombinationer av knapparna skulle datorn visa information om en resulterande molekyl. Exempelvis, om man trycker in knapparna för väte och syre samtidigt kan datorn visa information om vatten. Den fysiska postern var kopplad till datorn via en makey makey som översätter posterns knappar till ”riktiga” tangenttryckningar som Scratch kan känna av.

Men, hur känner man av flera samtidiga tangenttryckningar i Scratch? Om man har tre knappar på postern, hur upptäcker man och hur hanterar man olika kombinationer av dem i sitt script?

En rättfram approach kan vara att helt enkelt ha nästlade OM-satser i stil med detta:

Om <knapp 1 intryckt> :
  (fall 1)
  Om <knapp 2 intryckt> :
    (fall 2)
    Om <knapp 3 intryckt> :
      (fall 3)
  Om knapp <3 intryckt> :
    (fall 4) 
Om <knapp 2 intryckt> :
  (fall 5) 
  Om <knapp 3 intryckt> :
    (fall 6)
Om <knapp 3 intryckt> : 
  (fall 7)

Med ovanstående pseudokod täcker man upp de 7 olika kombinationer av knapptryckningar som kan förekomma då minst en knapp är intryckt med tre knappar. Underförstått är att Om-satserna tolkas så snabbt att alla knappar man tryckte in är intryckta under hela behandlingen av dem.

Detta fungerar, absolut. Men det gäller att hålla tungan rätt i mun och om man vill lägga till en fjärde knapp måste man tänka till ordentligt så att allt blir rätt. För att inte tala om en femte. Därför valde jag att istället utforma det hela på ett annat sätt.

Om vi antar att varje knapp kan vara antingen intryckt eller inte så finns det sju olika kombinationer med minst en knapp intryckt som kan inträffa. Eftersom jag har lite vana av att arbeta med binära tal så insåg jag snabbt att talet 7 är det största som kan representeras med 3 binära siffror, eller bitar. Genom att låta de tre olika knapparna representera olika positioner i ett 3-siffrigt binärt tal kommer olika kombinationer av tryck att ge ett tal mellan 1-7 (0 innebär att ingen knapp är intryckt).

Dessa tal kan jag sedan översätta till positioner i en lista, där de olika ämneskombinationerna ligger lagrade. På position 1, 2 respektive 4 som motsvarar värdena på positionerna i ett tresiffrigt binärt tal ligger respektive grundämne. På övriga positioner i listan ligger resulterande molekyler. I sin enklaste form skriver programmet bara ut antingen det ämne man trycker på eller vilken molekyl man kan få om flera knappar trycks in:

enkel kemi

När programmet startar anropas funktionen fyll lista som rensar och sedan fyller i en tabell över ämnen och molekyler som värdena 1-7 skall motsvara. När en lista fylls i läggs alltid varje element in i slutet (om ej annat anges), så jag fyller i listan med nummer 1 först och nummer 7 sist. Här kan vi se att de tre grundämnena var väte(1), syre(2) och kol(4).

I huvudprogrammet inväntar vi en tangenttryckning och kontrollerar sedan vilka knappar som var intryckta. Variabeln tryck uppdateras genom att plussas på med 1, 2 respektive 4 för varje knapp som var intryckt. Sedan hämtar man motsvarande element ur listan och visar upp det.

Om man vill utöka med en fjärde knapp så behöver man komplettera i huvudprogrammet så att man plussar på värdet 8 när den knappen är intryckt, samt utöka tabellen med det nya grundämnet samt nya molekyler. För varje knapp som läggs till blir listan ungefär dubbelt så stor. Ändå är denna typ av lösning betydligt enklare till sin struktur än en som baseras på nästlade Om-satser.

Det som man i praktiken gör här är att översätta de tre knapparna till en binär representation, s.k. kodning. Sedan använder man denna kod för att hämta rätt information ur en lista. Att lösa problemet på detta sätt kräver lite förståelse om hur både listor och binära tal fungerar. Binära tal är något som finns med i läroplanen och listor är något som är centralt och väldigt användbart i programmering. Dessutom får man i detta exempel en koppling till kemiämnet. Jag kan lätt föreställa mig att man skulle kunna använda samma teknik i ett annat program som kombinerar matematik med programmering och ytterligare ett ämne som är föremål för posterns innehåll.

Man kan för övrigt använda liknande tekniker för att komma ifrån begränsningen med 6 ”tangenter” på framsidan av en makey makey genom att låta varje ”tangent” vara en position i ett 6-siffrigt binärt tal. Då kan man med enbart 6 anslutningar känna av 31 olika kretsar. Men det överlämnar jag som en övning åt läsaren att lösa.

 

Konsten att tänka efter före

I en grupp på Facebook dök för en tid sedan upp en fråga som handlade om ett gammalt problem från Kängurutävlingen 2011:

fridolin

Frågan som lyftes var om detta är ett problem som kan lösas genom programmering. Givetvis kastade jag mig över det hela och snodde på 5-6 minuter ihop en lösning som gav rätt resultat. Sedan reflekterade jag lite över de olika delarna i mitt hopkok och gjorde om den med lite bättre struktur.

Hur tar man sig an detta problem som en programmeringsuppgift? Om man vill göra det ”korrekt” med en lösning som fungerar för vilka kartor som helst så representerar man kartan som en graf och beräknar sedan den längsta vägen genom grafen mellan ingång och utgång. Om man däremot betänker att detta är en uppgift som det är tänkt att man inte ska behöva ha läst grafteori för att läsa så måste vi hitta en annan lösning.

Min snabblösning hade en enkel strategi: Börja vid ingången och gå slumpmässigt i varje korsning utan att återvända till en tidigare besökt korsning. Håll reda på hur många korsningar du passerar. Om du kommer till utgången, kom ihåg antalet korsningar. Om du kommer till en återvändsgränd, börja om. Upprepa tillräckligt många gånger och kolla sedan vilket som var det största antalet korsningar du passerade från ingång till utgång.

Jag simulerar helt enkelt ett antal ”random walks” genom tunnelsystemet. Systemet är inte större än att 1000 simulerade promenader borde hitta det maximala antalet korsningar att passera. Och 1000 simulerade promenader med en maxlängd på 16 per promenad går snabbt att simulera. Inte den elegantaste lösningen, men den funkar.

Ett centralt beslut i programstrukturen är hur man ska representera tunnelsystemet och hålla reda på vilka korsningar man besökt.

Min första ansats var att representera det som en graf med noder och bågar med hjälp av någon färdig datastruktur för Python. Jag insåg dock snabbt att tiden för att hitta rätt bibliotek, lära mig använda det och sedan använda det var för lång för mitt otåliga jag.

Så jag valde istället en lösning där jag numrerar noderna från 01 till 16 enligt följande:

         01
      02 03 04
   05 06 07 08 09
10 11 12 13 14 15 16

Ingången är alltså nummer 01 och utgången nummer 16. Sedan skapade jag en lista som representerar tunnelsystemet genom att hålla reda på vilka grannar varje korsning har:

g = [[],[3],[3, 6],[2, 4, 7],[3, 8],[6, 11],
[2, 5, 7, 12],[3, 6, 8, 13], [4, 7, 9, 14], 
[8, 15], [11], [5, 10, 12], [11, 6, 13],
[12, 7, 14], [13, 8, 15], [14, 9, 16], [15]]

Om man går in på index i i listan g så får man veta vilka grannar som finns till den korsningen. Exempelvis kan man från position 15 nå korsningarna 14, 9 och 16.

Hur skulle jag då hålla reda på vilka korsningar jag har besökt? Min första ansats var att definiera en mängd b (för besökta) där jag lägger in alla korsningar jag passerar. När jag sedan väljer en ny korsning att gå till så kontrollerar jag först att den inte redan fanns inlagd i b. Problemet med denna lösning är dock när man kommer till en återvändsgränd och alla grannkorsningar redan är besökta. Hur skall jag upptäcka det?

En lösning på det problemet är att först kolla om alla grannkorsningar redan finns i bmen någonstans där började jag känna att det blir för trasslig kod. En annan, något enklare, lösning är att begränsa antalet försök man gör att hitta en obesökt grannkorsning innan man ger upp och antar att man helt enkelt hamnat i en återvändsgränd.

Jag bestämde mig istället för en annan strategi. Istället för att hålla reda på vilka korsningar som är besökta tar jag helt enkelt bort alla korsningar jag passerar från listan g. För varje korsning som passeras kommer antalet tal i att minska tills dess att man antingen når korsning nummer 16, eller har en tom lista med möjliga grannar på sitt index i g. 

 

Hela koden blev till sist så här:

fridolin3

Variabeln anger antalet promenader genom tunnlarna. I början av varje promenad initieras g tillsammans med för att hålla reda på antalet passerade korsningar (=antal upplockade frön) och p som anger aktuell position (startar i position 1). Sedan upprepas while-slingan tills dess man når utgången eller en återvändsgränd. För varje varv i slingan tas först aktuell position bort från alla element i g så att man aldrig kan återvända dit. Därefter slumpas en ny korsning att gå till fram, ökar med 1 och man kollar om man kommit till utgången. För varje gång man når utgången lagras antalet korsningar man passerat i listan ans och i slutet lokaliseras det största värdet i den listan, d.v.s. det största antal korsningar man har passerat på sin väg genom tunnlarna.

Denna lösning fungerar som beskrivet och är bara ca 15 rader lång. Den innehåller inga större konstigheter rent språkligt: tilldelningar, två slingor och två villkorssatser. Det som är det centrala i lösningen och som är skillnaden mellan kort eller lång kod, komplicerad eller enkel kod – det är hur man representerar data internt. En annan strategi för att hålla reda på hur korsningarna sitter ihop eller vilka man har besökt skulle kunna göra koden väldigt mycket mer komplicerad och svåröverskådlig. Det finns säkert andra lösningar som är mer eleganta än min. Jag tycker ändå att lösningen som jag har presenterat här är godtagbar med tanke på den korta tid den tog att färdigställa.

Jag tycker inte alls att det här problemet i sig är lämpligt för högstadie- eller gymnasieelever att lösa på egen hand. Däremot skulle man kunna plocka bort väl valda delar av koden och ge den till dem att komplettera som en övning. Det skulle i så fall vara mer av en programmeringsövning än något som är matematiskt relevant. Men framför allt tycker jag problemet kan utgöra en utgångspunkt för vikten av att ha en bra representation av den data man skall arbeta med och hur ett dåligt val kan få kännbara konsekvenser för resten av koden.

Eller annorlunda uttryckt: Tänk efter före. Det hjälper 🙂

 

Variationsteorins roll i digital allmänbildning

Variationsteorin är en lärandeteori med stark anknytning till fenomenografi. I korthet; de som är riktigt bra inom ett område har en förmåga att kunna betrakta och förklara det utifrån flera olika perspektiv. Likaledes så är det enligt variationsteorin ett nödvändigt (men ej tillräckligt) villkor att få se saker ur flera olika perspektiv för att få en god förståelse och kunskap om dem.

Detta är något som definitivt gäller inom området digitalisering och även inom programmering. Digitaliseringens påverkan på samhälle och individ är så pass omfattande att det inte är något som låter sig förklaras med enstaka exempel och erfarenheter. För att få en god insikt så är detta något som behöver integreras på olika sätt i undervisningen, i olika ämnen, utifrån olika perspektiv, i olika utsträckning.

Även i programmeringsundervisning kan man tillämpa variationsteorin. Man kan jobba med kod på olika sätt: läsa kod, granska andras kod, översätta kod, jobba utifrån skelettkod m.m. Här tänkte jag nu ge ett konkret exempel på en programmeringsövning inspirerad av variationsteori.

Uppgiften är följande:

Vid ena långsidan av ett bord står n stycken stolar. På dessa sätter sig slupmässigt n stycken personer som vi kallar för person 1 — person n. Vad är sannolikheten för att person 1 och 2 hamnar bredvid varandra?

Denna uppgift handlar om kombinatorik och det är relativt enkelt att resonera sig fram till en formel för sannolikheten givet n. Om vi däremot ska lösa denna uppgift genom programmering (eftersom detta nu skulle vara ett exempel på en programmeringsövning) så kan man välja flera olika ansatser. De två mest uppenbara är:

  1. Simulera antal placeringar och håll reda på hur ofta person 1 och 2 hamnar bredvid varandra.
  2. Gå igenom alla möjliga permutationer och håll reda på hur ofta person 1 och 2 hamnar bredvid varandra.

Bägge dessa lösningsvarianter är relativt enkla att producera, men de fungerar på fundamentalt olika sätt. Den förstnämnda går igenom en slumpmässig sekvens av placeringar medan den andra går igenom alla möjliga placeringar. Är någon av dem mer ”rätt” än den andra?

Strategi nummer 2 kommer alltid att ge ett korrekt och precist svar. Problemet är att eftersom antalet permutationer är n! så kommer det ganska snart att bli svårt att göra en uttömmande sökning. Dels för att det kommer att ta väsentligt längre tid, men även för att det är en utmaning att gå igenom alla permutationer på ett minneseffektivt sätt.

Om man exempelvis använder programspråket Python finns i modulen statistics en funktion som producerar en lista med alla möjliga permutationer. När blir större än 10 kommer den listan väldigt snabbt att konsumera en stor del av det tillgängliga minnet och programmet som i teorin är bättre på att beräkna en exakt lösning blir oanvändbart.

Det finns alltså en brytpunkt när den uttömmande sökning som strategi 2 innebär bör bytas ut mot en simulering enligt strategi 1.

Vad har då detta med variationsteori att göra? De två olika strategierna som presenterats representerar olika sätt att lösa samma problem, med olika förutsättningar att lyckas då n blir större. Strategi 1 baseras på slump medan strategi handlar om systematisk prövning.

Man kan här ha en diskussion om inte bara vad som är korrekt utan även vad som är rimligt i olika sammanhang, och möjligheten till approximativa lösningar genom exempelvis simulering som digital teknik medger.

För att få en förståelse för digital teknik, möjligheter och begränsningar tror jag att det är viktigt att synliggöra och låta elever möta dessa begränsningar och bli medvetna om att varje lösning har en begränsning som man bör vara medveten om.

Varför ska skolan lära ut programmering?

En fråga som duggar allt tätare i olika sociala forum och medier på sistone är varför skolan överhuvudtaget ska lära ut programmering. I diskussionerna som uppstår förekommer det en del missförstånd (minst sagt), så låt oss rekapitulera vad syftet egentligen är.

Grundskolan har till uppgift att förbereda barn för att kunna fungera som ansvarstagande medborgare i ett samtida samhälle. Det är min tolkning av den mer formella formulering som återfinns i läroplanen.

Vi lever i ett samtida samhälle som har genomgått en digitaliseringsprocess de senaste decennierna, och den fortgår fortfarande. Allt fler tjänster blir digitala, den digitala närvaron i olika sociala sammanhang ökar, individanpassad information som styrs av algoritmer påverkar oss dagligen. Att inte utbilda skolbarn för denna verklighet skulle vara ett grovt systemfel.

Därför identifierade skolverket fyra viktiga områden när de föreslog nya skrivningar i läroplanerna som senare fastställdes av regeringen:

  • Förståelse för hur digitalisering påverkar individ och samhälle
  • Användning av, och förståelse för, digital teknik
  • Ett kritiskt och ansvarsfullt förhållningssätt till medier och information
  • Lösa problem och omsätta idéer i handling på ett kreativt sätt med användning av digital teknik

Jag tror de allra flesta kan vara överens om att dessa fyra punkter är vettiga inslag i den allmänbildning som grundskolan skall ge. Observera att programmering inte är omnämnt alls här, och det av en väldigt enkel anledning: programmering är inte ett mål.

Ändå handlar så mycket av debatten just om programmering – något man mycket väl kan förstå eftersom många lärare känner sig osäkra på det. Då är det viktigt att komma ihåg att programmering inte är ett mål utan ett medel. Man kan med fördel använda programmering för att lära sig möjligheter och begränsningar med digital teknik. Man kan också använda det för att arbeta med kreativ problemlösning. Vill man vara riktigt avancerad kan man låta elever använda algoritmer för innehållsfiltrering för att skapa en medvetenhet om hur ett informationsflöde kan skapas och påverkas.

Det finns kort sagt många saker man kan använda programmering till under grundskolan, men det står ingenting någonstans om att eleverna ska bli programmerare. Eller för den delen lära sig programmering i någon större utsträckning. Däremot står det att programmering skall användas som ett medel, ett verktyg.

Jamen, tänker du kanske då: för att använda programmering måste man ju först kunna programmering! Och det är kanske där det blir lite fel ibland. För det går alldeles utmärkt att använda enkla, basala koncept från programmering till lärande i andra ämnen utan att för den skull behöva lära sig programmering fullt ut. Det går alldeles utmärkt att låta elever arbeta med given kod, experimentera med den och utforska både programmering och ett ämne som koden handlar om samtidigt.

Man behöver inte vara expert på programmering för att använda det i sin undervisning.

Däremot så underlättar det givetvis om man behärskar några väldigt grundläggande koncept. Resten, det är träning.

Och det är också därför det är så viktigt att elever får börja träna på programmering redan tidigt och sedan öva på det i olika sammanhang, med olika verktyg, i olika miljöer genom åren. På det sättet kan de bygga upp en baskompetens inom programmering samtidigt som de använder den för att lära sig andra saker.

Om inte det känns meningsfullt i en samtida grundskoleutbildning, då vet jag inte riktigt vad som skulle vara det.

Det utesluter inte att det finns en massa annat som också är viktigt. Men att försöka få det till att programmering inte är ett relevant verktyg i undervisningen idag – det håller inte. Snarare är det så att vi ligger väldigt sent ute med den utvecklingen.

Hjälp, jag kan inte programmera!

Det är många lärare som uttrycker en oro över hur de ska vara kapabla till undervisning där deras elever får programmera med hänvisning till sina egna begränsade kunskaper och erfarenheter.  Jag har full förståelse för denna oro och vill inte på något sätt bagatellisera den men vill ändå hävda att den till viss del är omotiverad eftersom jag är övertygad om att i princip alla lärare kan tillföra mer än de kanske själv inser vid en första anblick.

Låt oss börja med begreppet programmering. Ett problem som ofta uppstår när man diskuterar programmering är att det råder delade meningar om vad som egentligen ingår. Därför vill jag klargöra att jag ser programmering som en kreativ problemlösningsprocess där man går från en problemsituation till att formulera en problemställning. Till denna tar man sedan fram en lösningsstrategi, ofta i form av en algoritm som man sedan kodar, testar och utvärderar.

Det är många som likställer programmering med själva kodningsdelen av hela processen och när de uttrycker att de inte kan programmera så menar de nog(?) egentligen att de inte har särskilt stor vana av kodning – om ens någon i vissa fall. Och så kan det givetvis vara, men om man anammar den mer processorienterade bilden av vad programmering är så betyder det i praktiken att man kanske inte har så stor erfarenhet av att koda – vilket bara är en liten del av det hela. Faktiskt så är det den del som inte sällan är den minst problematiska; utmaningarna i programmeringssammanhang brukar oftast handla om att ta fram en fungerande lösningsstrategi och formulera den som en algoritm. Visst, med begränsad erfarenhet av kodning har man säkert också begränsad erfarenhet av det men mer sannolikt är att man har många erfarenheter av problemlösning i andra sammanhang. Erfarenheter som är värdefulla i undervisningssammanhang även om man tidigare inte tillämpat dem för att koda.

Programmering i grundskolan är integrerat i framför allt matematikämnet och jag har svårt att tänka mig en matematiklärare som inte har erfarenhet av att generalisera och formulera ett matematiskt uttryck för ett givet problemscenario. Vilket faktiskt är vad programmering till mångt och mycket handlar om.

När det gäller själva kodningsbiten så skulle jag vilja uppmana alla oroliga lärare att inte ta för allvarligt på den. Kanske har du inte så mycket erfarenhet av en specifik programmeringsmiljö eller kodning i allmänhet, men du har andra erfarenheter och kompetenser som är värdefulla vid programmering. Med en inställning om att kodningsbiten är något som du i någon utsträckning lär dig tillsammans med dina elever kommer det att gå bra. Du kommer ändå att kunna bedriva meningsfull undervisning genom att dra nytta av de erfarenheter du har inom andra områden.

För givet att man accepterar programmering som en process som består av flera olika delar så innebär det inte bara är kodning som är det enda sättet på vilket man kan arbeta med programmering. Här tänker jag nu utgå från att eleverna redan är bekanta med centrala koncept och principer inom programmering och exemplifiera hur man på några olika sätt kan träna på programmering integrerat i matematikundervisning utan att behöva vara en mästare på att koda.

Studera algoritmer

När man ska formulera lösningsstrategier som en algoritm gäller det att kunna uttrycka sig med de ”byggstenar” som finns att disponera. Ett sätt att träna sig på detta är att helt enkelt studera befintliga algoritmer. Och en bra ansats för det är sorteringsalgoritmer.

Insertion sortBubble sort och Selection sort är relativt enkla att börja med. Hitta beskrivningar av dessa algoritmer i form av kod – företrädelsevis i det programspråk ni använder er av. Låt eleverna läsa koden. Förstår de hur den fungerar? Tror de på den? Låt eleverna ”köra” koden genom att följa algoritmen som är uttryckt i kod och använda papperslappar med heltal på som ska sorteras.

Formulera en egen algoritm

När eleverna fått en känsla för vilka delar dessa enkla algoritmer är uppbyggda kring kan du be dem formulera en egen algoritm – antingen i form av pseudokod eller som kod i det språk ni använder er av. Koden behöver inte skrivas in i en programmeringsmiljö och köras – det kan räcka att skriva ned den på papper. Låt eleverna arbeta i grupper som byter algoritmer med varandra. Kan en grupp lista ut vad en algoritm från en annan grupp gör genom att bara läsa den? Genom att simulera den, exempelvis med papperslapparna igen.

Skapa en formel

Hitta ett enkelt problem där lösningen kan uttryckas med hjälp av en matematisk formler. Ge eleverna problemet och be dem formulera en lösning i grupp. Låt grupperna presentera sina olika lösningar och diskutera om det fungerar, hur de fungerar, hur de kan användas.

Byt kod med varandra

Om eleverna kodar, låt dem då och då byta kod med varandra. Det kan vara en bra träning i att sätta sig in i någon annans kod – någon som kanske har tänkt annorlunda och tagit sig an problemet på ett annat sätt än en själv. Detta kan vara en riktigt utmanande men också lärorik övning!

Koda aldrig åt eleverna

En bra tumregel vid programmeringsundervisning kan vara att aldrig koda åt eleverna. Om de kör fast, be dem förklara med egna ord hur de har tänkt sig att programmet ska fungera. Är strategin vettig? Tror de på den? Tror du på den? Finns det specialfall som har missats? Kan de simulera koden för att dubbelkolla att den verkligen fungerar? Ställ frågor som hjälper dem i rätt riktning men skriv aldrig kod åt dem.

 

 

 

Dags att rösta !?

Idag, på morgonen den 21:a Mars loggade jag in på Facebook och möts då av följande personligt anpassade meddelande:

29432514_10156328911946757_1834832774997999616_o

Jag blev ju lite ställd. Jag vet ju att det är valår i år, men mig veterligt inträffar valdagen om ett knappt halvår. Men tydligen så är det valdag idag och Facebook vill att jag ska dela med mig av att jag har röstat och kan även vara behjälpliga i att hitta till vallokalen.

När den första förvåningen lagt sig klickade jag på ”Hitta vallokal” och fick veta att det faktiskt är valdag idag. I Nederländerna.

Varför tror då Facebook att jag är i Nederländerna? Kanske för att jag två dagar tidigare hade mellanlandat på Schiphol på väg hem från Taiwan? Det är den enda rimliga förklaring jag kan hitta.

Jag reser inte särskilt mycket utanför landet, men förra veckan flög jag till Taiwan via Amsterdam. Det var första gången på minst 10 år som jag mellanlandar i Amsterdam men det räckte tydligen för att Facebooks algoritmer ska tycka att det är viktigt för mig att få veta att det är valdag idag. I Nederländerna alltså.

Det här är ett exempel på när en algoritm har fallerat i att bidra med något konstruktivt och meningsfullt till dess mottagare. Visst, det är en rolig anekdot i någon utsträckning. Men i en tid då aktörer som Google och Facebook försöker få deras tjänster att bli mer personligt anpassade och interaktiva så krävs det ju att algoritmerna bakom dem fungerar.

En positiv sak med att råka ut för ett sånt här misstag är ju att man blir påmind om att det finns begränsningar i algoritmerna som styr en tjänst som försöker framstå som intelligent och individuellt anpassad. Och det är ju aldrig fel att komma ihåg.

Varför vänta till vt 19?

Skolverket har nu lagt upp information om de planerade kurserna i grundläggande programmering för ämneslärare i högstadiet och gymnasiet. Och jag har redan hunnit få samma fråga minst 3 gånger. Samma fråga som är en fullt legitim fråga och därför tänkte jag utveckla den lite här.

Frågan är: varför väntar ni i Uppsala till våren 2019 innan ni erbjuder någon kurs?

Ja, det kanske kan tyckas märkligt. När 13 lärosäten runt om i landet kan erbjuda en kurs redan hösten 2018, varför kan inte vi i Uppsala göra det? Varför måste vi vänta ett halvår längre?

Svaret är enkelt: vi tror inte att vi hinner med att utveckla en bra kurs på den korta tid som finns om den skall ges redan hösten 2018. Detta är på intet sätt en pik till de som erbjuder en sådan kurs – att det inte skulle vara en bra kurs bara för att den går hösten 2018. Förutsättningarna för planering är antagligen olika vid olika lärosäten och de personer som är iblandade i kursplaneringen har med säkerhet olika årsplaneringar.

Vi skulle mycket väl kunna erbjuda en kurs hösten 2018 genom att ta en väl inarbetad grundkurs i programmering och krydda den med lite didaktik och ämnesdidaktisk forskning. Men det vi försöker eftersträva är inte att ta en grundkurs i programmering för ingenjörsstudenter och lappa ihop den så att den passar praktiserande lärare. Vi har helt enkelt högre ambitioner än så. Vi vill skapa en kurs som genom hela planeringsarbetet har lärare som målgrupp och där didaktiska och pedagogiska frågeställningar är integrerade genom hela kursen.

Eftersom de här kurserna skiljer sig från de kurser vi normalt ger på campus genom att ha ett begränsat antal träffar och mycket eget arbete mellan träffarna så krävs det en ganska omfattande planeringsprocess för att få det bra. Vi skulle kanske ha kunnat få det klart till hösten 2018, men det hade blivit väldigt tight och därför satsar vi på våren 2019 istället. Turligt nog finns det många andra lärosäten som också erbjuder kurser enligt samma ramkursplan så det kommer nog att bli bra ändå.

Det som förvånar mig mest är att inget lärosäte i Stockholmsområdet planerar att ordna en kurs i grundläggande programmering för lärare på gymnasial nivå. Jag kan tänka mig att det finns ett behov av just den typen av kurs som inte matchas av de upphandlade kurserna. Men det kanske finns andra lösningar på det, vi får väl se.

 

 

Sannolikheten för Yatzy

Johan Falk från Skolverket gav på Matematikbiennalen 2018 en presentation som innehöll exempel på problem som lämpligen löses med programmering. Ett av dessa problem var att räkna ut sannolikheten för att man får Yatzy efter tre kast där man efter varje kast själv får välja vilka tärningar man vill spara.

Jag tyckte det lät som ett bra exempel, så jag satte mig ner för att skissa ihop ett program som räknar ut just detta. Den här bloggposten handlar om hur det programmet växte fram och vilka lärdomar jag drog längs vägen.


Till att börja med funderade jag över dels hur jag skulle representera fem stycken tärningar i mitt program, dels hur jag ville presentera resultatet. Rätt snabbt landade jag i att tärningarna får representeras med en lista som har 5 element med värden från 1-6 på varje position. I Python finns det många bra funktioner för att hantera listor så det blir nog bra tänkte jag.

Min första ansats var att för varje runda kasta tärningarna ända tills jag fick yatzy och sedan lagra antalet kast som krävdes i en resultatvektor. Ur den kunde jag sedan räkna antalet 1:or, 2:or och 3:or för att få fram sannolikheten för yatzy på tre kast men också se hur fördelningen ser ut om man har obegränsat med kast.

Pseudokoden för detta:

Resultat = []
Upprepa n gånger : 
  Kast = 0 :
  Upprepa tills alla tärningar är lika :
    Kasta tärningar
    Välj ut vilka tärningar som skall sparas
    Kast = Kast + 1
  Lägg till Kast i Resultat

En kontroll av att alla tärningar är lika kändes som om det går att lösa med ett villkorsuttryck. Däremot är kast av tärningar och urval av vilka som skall sparas inte lika rättfram så dessa två bestämmer jag mig för att placera i två egna funktioner.

Först har vi funktionen för att kasta tärningar. Rimligtvis skall den ta min lista med tärningar som indata, men den behöver också veta vilka tärningar som skall sparas. Först tänkte jag att jag på något sätt markerar de tärningar jag vill spara, men bestämde mig sedan för den enklare lösningen där jag helt enkelt håller reda på vilket värde på tärningar jag vill spara:

def throw(d ,s) : 
   Resultat = []
   Låt i gå från 1 till 5 
     Om d[i] == s 
       Lägg till s i Resultat
     Annars
       Lägg till ett slumptal 1-6 i Resultat
   Returnera Resultat

Funktionen bygger upp en resultatlista där de tärningar som redan har värdet s bibehålls och övriga får ett nytt slumptal 1-6.

För att välja ut vilka tärningar som skall sparas bygger jag upp en lista som räknar antalet tärningar med olika värden. Ur denna lista plockar jag sedan det högsta värdet, under förutsättning att det är större än 1 – annars skall ju ingen tärning sparas:

def savewhat(d) :
  Resultat = []
  Låt x gå från 1 till 6
    Räkna förekomsten av x i listan d
    Lägg in svaret i Resultat
  Om största värdet i Resultat är > 1
    Returnera positionen för största värdet
  Annars
    Returnera 0

Eftersom Resultat är en lista och jag alltid lägger in i slutet av listan så vet jag att det första värdet i Resultat är antalet 1:or, det andra antalet 2:or etc. Därför kan jag få reda på värdet av det jag vill spara genom att returnera positionen för det största värdet i en lista som räknar hur många gånger varje värde förekommer.

Med dessa funktioner på plats kan jag nu sy ihop allt. Efter lite extra spårutskrifter för att försäkra sig om att det fungerar kan jag konstatera att jag kan få fram fördelningar och räkna ut en sannolikhet för Yatzy om jag efter varje kast sparar de tärningar jag har flest av.


Det går dock ganska långsamt om man vill köra många rundor, så jag bestämmer mig för att istället för att kasta tills jag får Yatzy helt enkelt kasta tre gånger och se om jag fick Yatzy eller inte:

def Yatzy():
  tärningar = [1,2,3,4,5]
  upprepa 3 gånger :
    välj ut vad som skall sparas
    kasta tärningarna
  returnera (antalet lika tärningar == 5)

Denna kod kör mycket snabbare, vilket är uppskattat om man vill köra 10000 rundor per omgång och 100 omgångar.

För att lättare kunna analysera mina resultat skriver jag en funktion som kör n rundor med t omgångar i varje runda:

Resultat = []
Upprepa n gånger: 
  c = []
  Upprepa t gånger:
    Lägg in resultatet från Yatzy() i c
  Räkna antalet "True" i c och dela med t
  Lägg in svaret i Resultat

Detta ger mig en svarslista med n element där varje element visar sannolikheten för Yatzy efter att ha kört t omgångar med tre kast. Listan kan jag direkt använda med de statistikfunktioner som finns för att räkna ut medel, median, varians och olika spridningsmått. Den statistiska signifikansen är stark och jag får fram ett svar på frågan.


Nu bestämmer jag mig för att jämföra detta svar med det svar man får fram om man istället räknar ut sannolikheten för Yatzy analytiskt. Det visar sig att de inte överensstämmer.

Efter att ha granskat koden, försäkrat mig om att simuleringen är korrekt och kört med stora värden på såväl n som står det klart att simuleringen ger ett annat svar än det analytiska. Vad är fel?

Efter ett tag slår det mig att problemet ligger i den modell jag har använt mig av om hur man spelar Yatzy. Efter varje kast sparar jag alltid de tärningar jag har flest av. Om du efter ett kast har tre tvåor och två femmor så sparar jag tvåorna och hoppas på att bara behöva slå två tärningar. Men jag skulle lika gärna ha kunnat spara femmorna och slå tre tärningar – det kan också ge Yatzy, bara med mindre sannolikhet. Det är något som tas hänsyn till vid en analytisk beräkning av sannolikheterna, men inte vid en simulering där man i varje steg bara sparar de tärningar man har mest av.

Jag satte mig ner och funderade över hur jag skulle modifiera programmet för att simulera fram ett resultat som överensstämmer med det analytiska. Jag har provat med att med olika sannolikheter inte alltid spara de tärningar jag har flest av. Jag har provat med att först kasta tre gånger och sedan se hur jag på olika sätt kan kombinera ihop resultaten från kast till Yatzy. Och några andra lösningar. Men det blir ganska snabbt väldigt snårigt och komplext och landar till sist i att man i princip gör samma sak som man gör vid en analytisk lösning av problemet och då kunde man ju lika gärna ha låtit bli att gå omvägen via programmering.

Så istället får man nog betrakta detta problem som ett där det är svårt för elever att producera en simulering som ger ett svar som överensstämmer med teorin. Men kring detta kan man också ha en del intressanta diskussioner. Bara att diskutera kring varför skillnaderna uppstår kan bli en meningsfull diskussion. Man kan också fundera över vilka begränsningar som kan finnas i simuleringar jämfört med analytiska metoder, samt vikten av att ha en korrekt modell i sina simuleringar.

Så på det hela taget tycker jag ändå det är en bra uppgift, men att den i så fall skall följas upp med en diskussion om varför man får det svar man får.

Koden ovan är bara psuedokod. Om du vill se hela koden (Python 3) så finns den här.

En till synes enkel övning

Här tänkte jag utveckla en enkel programmeringsövning som jag brukar ta upp en bit in i en introduktionskurs till programmering.

Problembeskrivningen är som följer:

Skapa en funktion som tar två argument x och y och sedan returnerar en lista med x st slumptal mellan 1 och y. Inget tal får förekomma mer än en gång i listan. 

Detta är något som sedan kan vara användbart i många olika fall när man vill ha en sekvens av slumptal att använda för att testa ett program man har skrivit. Eller om man vill ta fram en lottorad för den delen (x=7, y=35).

I all sin enkelhet så inkluderar denna till synes enkla övning några små utmaningar som man måste hantera:

  1. Hantering av listor
    Hur skapar man en lista? Hur arbetar man med den? Vad kan man göra med den?
  2. Användning av slumptal
    Hur skapar man slumptal?
  3. Användande av funktionsargument
    Om en funktion tar två argument x och y, hur använder man dem i sin kod?
  4. När ska man sluta?
    Hur får man funktionen att skapa x stycken unika slumptal?
  5. Dubletter
    Hur går man tillväga för att undvika förekomst av dubletter i den resulterande listan?

En intressant aspekt är också att det här i all sin enkelhet ändå går att lösa på flera olika sätt genom att kombinera slingor och villkor på olika sätt. Särskilt när det gäller att undvika dubletter kan det lätt bli lite snårigt där man lätt tappar bort sig.

Punkt 1-3 ovan är tämligen okomplicerade i den meningen att det mest handlar om något som man måste lära sig. Punkt 4 och 5 inbjuder däremot till olika lösningar på ett mer konkret sätt så det är främst de jag kommer att koncentrera mig på.


Vi vet att det är stycken slumptal som skall tas fram. Därför är den lösning som känns mest intuitiv att ha en FOR-slinga som upprepas x antal gånger. För varje varv i slingan tar vi fram ett nytt slumptal som läggs in i listan.

Problemet här är att vi dessutom måste säkerställa att varje slumptal som vi tar fram inte redan finns i listan sedan tidigare. I så fall måste vi ta fram ett nytt slumptal innan vi går vidare till nästa varv i FOR-slingan. Det landar i att vi inne i FOR-slingan behöver ha ytterligare en slinga som upprepas till vi hittar ett slumptal som inte redan finns i listan.

Det kan se ut så här (jag använder här Python som exempelspråk) :

import random

def tal(x, y) : 
  lista = []                  
  for _ in range (0, x) :
    tal = random.randint(1, y) 
    while tal in lista : 
      tal = random.randint(1, y)
    lista.append(tal)
  return lista

Notera att man måste skapa en kandidat tal innan den inre WHILE-slingan för att villkoret skall fungera den första gången. Sedan upprepas WHILE så länge man tar fram tal som redan finns i listan. När WHILE-slingan avslutas kan vi lägga in det senaste framslumpade talet i listan.

Även om denna lösning är relativt enkel så finns det en del fällor att gå i, framför allt när det gäller nästlade slingor. Finns det något annat sätt man kan lösa detta på utan att använda en FOR-slinga som skall utföras x antal gånger?

Ja, det finns det. Ett alternativ är att använda en WHILE-slinga som upprepas tills dess att listan har fått rätt längd:

import random

def tal(x, y) :
  lista = []
  while len(lista) < x :
    tal = random.randint(1, y)
    if not tal in lista : 
      lista.append(tal)
  return lista

Eftersom WHILE-slingan inte är låst till att upprepas exakt x antal gånger kan koden innanför ha en enklare struktur. För varje varv i slingan så tar vi fram ett slumptal. Om slumptalet inte redan fanns i listan så läggs det till. Och så fortsätter det fram tills dess att listan har fått x tal instoppade.

Den här lösningen är lite svårare att trassla in sig i eftersom den är mer rättfram. Men går det att förenkla ännu mer?


I ärlighetens namn är denna lösning inte särskilt komplicerad i sig, men jag tänker ändå ta upp ytterligare en lösning där vi använder oss av mängder (set) för att hantera dubletterna. En mängd kan ju bara innehålla ett värde en gång, så om vi försöker sätta in en dublett kommer det helt enkelt att ignoreras.

En lösning som baseras på mängder kan se ut så här:

import random

def tal2(x,y) :
  lista = set()
  while len(lista) < x :
    lista.add(random.randint(1, y))
  return list(lista)             # Konvertera mängd till lista

Den första raden i funktionen skapar en tom mängd. Den sista raden omvandlar mängden till en lista eftersom det i uppgiften var givet att det var en lista som skulle returneras. Typkonvertering är något som kan vara komplicerat i en del språk medan det i Python är relativt enkelt.

Dessa tre lösningar som jag har presenterat ovan bygger på att man vet att man kan lägga till element i en lista med metoden append , att len-funktionen returnerar antalet element i en lista samt att operatorn in kan användas för att kontrollera om ett element finns i en lista. Om man inte känner till detta så kan man själv hantera listan helt manuellt genom att lägga in ett nytt tal i slutet, hålla reda på listans längd med en lokal variabel och stega igenom listan på jakt efter dubletter. Det fungerar det med, men då blir koden mer omfattande och med fler fallgropar, så det finns definitivt en poäng i att lära sig behärska de mest grundläggande funktionerna som finns på en lista.

Vilken lösning är då ”bäst”? Det är en smaksak skulle jag säga. Personligen tycker jag att den sistnämnda är mest lättläst, vilket är en egenskap som många gånger är underskattad. Men ytterst är det som sagt en fråga om tycke och smak. I just detta fall tycker jag det är intressant hur en till synes enkel uppgift kan lösas på många olika sätt, vilket kan vara en utmaning för både elever och deras lärare.

Designa en webbplats som denna med WordPress.com
Kom igång