Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 8.2: Robotteja ja olemattomia arvoja
Tästä sivusta:
Pääkysymyksiä: Miten käytän Option
-tyyppiä kätevästi?
Miten saan luvun 8.1 robottiprojektin täydennettyä toimivaksi?
Mitä käsitellään? Naitamme Option
in ja kokoelmien korkeamman
asteen metodit. Työstämme robottisimulaattoria eteenpäin.
Mitä tehdään? Luetaan ja ohjelmoidaan. Lopussa läjä kokoavia monivalintoja.
Suuntaa antava työläysarvio:? Vajaat pari tuntia.
Pistearvo: B45.
Oheisprojektit: Robots.
Robottien toimintavuorot
Robottimaailman advanceTurn
- ja advanceFullTurn
-metodien olisi tarkoitus pyörittää
toimintavuoroa robottien välillä. Niitä avustaa RobotBody
-luokan takeTurn
-metodi,
joka määrää robotin käyttämään yhden toimintavuoron.
Lähdetään toteuttamaan viimeksi mainittua. RobotBody
-luokasta löytyy metodiaihio:
def takeTurn() = {
if (this.isMobile) {
// TODO: call the brain's moveBody method (if there is a brain)
}
}
Tämä pikkutehtävä toimii kertauksena ja johdattaa seuraaviin aiheisiin:
Olisi kiva, jos yksinkertaisen asian voisi sanoa yksinkertaisesti. Muun muassa siksi
pidämme nyt tauon roboteista ja opettelemme hieman uutta. Palaamme sitten robottimaailmaan
Option
tanassa.
Luvunetsimisongelma
Otetaan erillinen esimerkki. Sanotaan vaikkapa, että haluamme löytää kokonaislukuvektorista ensimmäisen suuren luvun (tässä: yli 10000) ja ensimmäisen negatiivisen luvun sekä selvittää, ovatko löydetyt luvut parillisia.
Etsiminen sujuu kokoelmaolion find
-metodilla. Tämä metodi palauttaa Option
-tyyppisen
arvon:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) val isoJosLoytyi = luvut.find( _ > 10000 )res0: Option[Int] = None val negatiivinenJosLoytyi = luvut.find( _ < 0 )res1: Option[Int] = Some(-20)
Option[Int]
-tyyppiselle oliolle eli "luvulle, joka ehkä on ehkä ei", ei voi määrittää
jakojäännöstä %
-operaattorilla:
negatiivinenJosLoytyi % 2 == 0<console>:10: error: value % is not a member of Option[Int]
Ongelma on siis sama kuin äsken takeTurn
-metodissa: haluamme suorittaa toimenpiteen
vain, jos tarvittava arvo on olemassa.
Ongelman jäsennys
On kolme eri tapausta: luku löytyi ja on parillinen, luku löytyi ja on pariton, tai lukua ei löytynyt, joten sen parillisuus on määrittelemätön. On laadittava koodi siten, että nämä kaikki kolme tapausta tulevat käsitellyiksi.
Tapauksien esittämiseen sopii tietotyyppi Option[Boolean]
. Päätetäänkin siis tuottaa
tuloksia näin:
- Jos
find
palauttaaNone
, niin lukua ei löytynyt, joten tuloskin onNone
. - Jos
find
palauttaaSome
, ja sen sisällä on parillinen luku, niin tuotetaan tuloksenaSome(true)
. - Jos
find
palauttaaSome
, ja sen sisällä ei ole parillinen luku, niin tuotetaan tuloksenaSome(false)
.
Yksi mahdollisuus olisi käyttää match
-käskyä:
Kelvollinen ratkaisu match
illä
val isoJosLoytyi = luvut.find( _ > 10000 )
val isonParillisuus = isoJosLoytyi match {
case Some(loytynytIso) => loytynytIso % 2 == 0
case None => None
}
Kuten aiempi takeTurn
-toteutus, tämäkin ohjelmakoodi aika monisanainen ottaen huomioon,
kuinka yksinkertaisesta tarpeesta loppujen lopuksi on kysymys. Helpommallakin pääsee.
Otetaan uusi näkökulma Option
-luokkaan.
Option
alkiokokoelmana
Option
-tyyppinen olio on itse asiassa kokoelma: se sisältää jonkin määrän tietyntyyppisiä
alkioita. Se on vain kokoelmaksi kovin yksinkertainen: alkioita on joko nolla tai yksi.
None
on nolla-alkioinen alkiokokoelma, ja Some(x)
on yksialkioinen kokoelma, jossa
alkiona on x
.
Myös Scala-kirjastojen suunnittelijat ovat ajatelleet Option
-luokkaa kokoelmatyyppinä.
Luokka on nimittäin laadittu siten, että sillä on koko liuta aivan samoja metodeita
kuin muillakin kokoelmilla! Niiden kutsuminen kätevöittää Option
-luokan käyttöä usein
kummasti.
Kokeillaan ensin vaikkapa Some
-oliolla eli yksialkioisella kokoelmalla.
Some
alkiokokoelmana
foreach
-metodille annettu parametrifunktio suoritetaan Some
-arvon sisältämälle arvolle:
val kokeilu = Some("Täs mä nyt oon")kokeilu: Some[String] = Some(Täs mä nyt oon) kokeilu.foreach(println)Täs mä nyt oon
filter
-metodi palauttaa alkuperäisen Some
n tai None
:
kokeilu.filter( _.length < 100 )res2: Option[String] = Some(Täs mä nyt oon) kokeilu.filter( _.length >= 100 )res3: Option[String] = None
exists
- ja forall
-metodit toimivat luonnolliseen tapaan:
kokeilu.exists( _.length >= 100 )res4: Boolean = false kokeilu.exists( _.length < 100 )res5: Boolean = true kokeilu.forall( _.length < 100 )res6: Boolean = true
map
-metodilla voi tuottaa toisen Some
-olion, jossa on alkuperäisen Some
-olion
sisältämän arvon perusteella laskettu toinen arvo:
kokeilu.map( _.toUpperCase + "!!!" )res7: Option[String] = Some(TÄS MÄ NYT OON!!!)
None
alkiokokoelmana
Kokeillaan sitten None
-oliolla eli tyhjällä kokoelmalla. Kuten muillekaan tyhjille
kokoelmille, foreach
-metodi ei tee None
-oliolle mitään:
val kokeilu2: Option[String] = Nonekokeilu2: Option[String] = None kokeilu2.foreach(println)
filter
ja map
palauttavat aina None
, koska minkäänlaista alkiota ei ole:
kokeilu2.filter( _.length < 100 )res8: Option[String] = None kokeilu2.map( _.toUpperCase + "!!!" )res9: Option[String] = None
exists
palauttaa vastaavasti aina false
:
kokeilu2.exists( _.length >= 100 )res10: Boolean = false kokeilu2.exists( _.length < 100 )res11: Boolean = false
forall
-puolestaan palauttaa aina true
, koska mikä tahansa ehto pitää paikkansa
kaikille nollalle alkiolle. Tai ehkä paremmin sanoen: kun alkioita on nolla, ei ole
yhtään sellaista arvoa, jolle annettu ehto ei olisi voimassa, oli ehto sitten mikä
tahansa.
kokeilu2.forall( _.length >= 100 )res12: Boolean = true
Luvunetsimisongelman näppärämpi ratkaisu
Oletetaan taas annetuiksi nämä käskyt:
val luvut = Vector(10, 5, 4, 5, -20)luvut: Vector[Int] = Vector(10, 5, 4, 5, -20) val isoJosLoytyi = luvut.find( _ > 10000 )res13: Option[Int] = None val negatiivinenJosLoytyi = luvut.find( _ < 0 )res14: Option[Int] = Some(-20)
Annetaan map
-metodille parametriksi parillisuuden selvittävä funktio. Toisin sanoen
käsketään: "Selvitä parillisuus jokaiselle find
-metodin palauttaman Option
-olion
sisältämälle alkiolle."
isoJosLoytyi.map( _ % 2 == 0 )res15: Option[Boolean] = None
Nyt jos sattuu käymään niin, että Option
on tyhjä, on tuloskin None
kuten yllä.
Jos taas Option
-kääre sisältää arvon, sovelletaan parillisuudenselvitysfunktiota
tuohon arvoon ja saadaan tulos Some
-olion sisällä:
negatiivinenJosLoytyi.map( _ % 2 == 0 )res16: Option[Boolean] = Some(true)
map
-metodia käyttämällä voimme siis huomioida kaikki kolme tapausta: "löytyi
parillinen", "löytyi pariton" ja "ei löytynyt mitään".
Yllä käytettiin ensiesimerkin selkiyttämiseksi välivaiheita, mutta lyhyemminkin voi toki kirjoittaa:
luvut.find( _ > 10000 ).map( _ % 2 == 0 )res17: Option[Boolean] = None luvut.find( _ < 0 ).map( _ % 2 == 0 )res18: Option[Boolean] = Some(true)
tempo
-esimerkki
Tässä toinen esimerkki Option
-olion map
-metodin käytöstä. Se on eräs toteutustapa
luvussa 4.5 esillä olleelle tempo
-funktiolle:
def tempo(kappale: String) = kappale.split("/").lift(1).map( _.toInt ).getOrElse(120)tempo: (kappale: String)Int tempo("cccedddfeeddc---/150")res19: Int = 150 tempo("cccedddfeeddc---")res20: Int = 120
split
(luku 4.5) ja lift
(luku 4.2). Edellisellä jaetaan merkkijono osiin kauttamerkin
kohdalta. Jälkimmäisellä tuotetaan Option[String]
, joka
sisältää kauttamerkin jälkeisen osan, jos sellaista oli.lift
-metodikutsun palauttama mahdollinen merkkijono saadaan
muutettua mahdolliseksi kokonaisluvuksi käyttämällä metodeita
map
ja toInt
.Preferences
-esimerkki
Tavoite: valinnaisia asetuksia käyttäjäprofiileissa
Olkoon kuvitteellisessa sovelluksessa seuraava luokka, joka kuvaa käyttäjän henkilökohtaisia
asetuksia. Ohjelmassa on vain kaksi eri käyttäjäasetusta, jotka kertovat käyttäjän suosiman
kielen ja sen, käytetäänkö SI-järjestelmän mittayksiköitä vai ei. Kumpikin asetuksista on
valinnainen, eli käyttäjä saattaa olla ilmoittanut mieltymyksensä kyseisestä asiasta tai ei.
Asia on kuvattu Option
-olioilla:
class Preferences(val profileName: String, val language: Option[String], val metricSystem: Option[Boolean]) {
// tänne toString-metodi yms.
}
Tässä pari käyttöesimerkkiä:
val test = new Preferences("My preferred settings", Some("English"), None)test: Preferences = lang: English, metric: NOT SET val test2 = new Preferences("Some other settings", Some("Finnish"), Some(true))test2: Preferences = lang: Finnish, metric: true
Olkoon vielä niin, että käyttäjä joko on tehnyt itselleen asetusprofiilin, jota kuvaamaan
on tallennettu Preferences
-olio, tai sitten hän ei ole, jolloin Preferences
-oliotakaan
ei ole. Voimme kuvata asiaintilaa muuttujalla, jonka tyyppi on Option[Preferences]
.
Alla on kolme erillistä esimerkkiä: Tiina on tehnyt asetukset mukaan lukien kieliasetuksen,
Tainallakin on asetukset muttei kieliasetusta, ja Teemu ei ole tehnyt asetuksia laisinkaan:
val tiinasPreferences = Some(new Preferences("Tiina's profile", Some("Finnish"), Some(true)))tiinasPreferences: Some[Preferences] = Some(lang: Finnish, metric: true) val tainasPreferences = Some(new Preferences("Taina's profile", None, Some(true)))tainasPreferences: Some[Preferences] = Some(lang: NOT SET, metric: true) val teemusPreferences: Option[Preferences] = NoneteemusPreferences: Option[Preferences] = None
Miten voidaan tuottaa String
-tyyppinen arvo, joka kertoo, millä kielellä ohjelman
käyttöliittymä tulisi esittää käyttäjälle? Halutaan, että mikäli käyttäjällä on tallennetut
asetukset ja niihin on tallennettu language
-tieto, niin käytetään kyseistä kieltä. Jos
asetukset puuttuvat tai jos language
-asetus on None
, käytetään ohjelman oletuskieltä
(joka olkoon vaikkapa englanti).
Hahmotellaan REPLissä.
Ratkaisun hahmottelua
Tämä voisi olla ensimmäinen yritys selvittää Tiinan suosima kieli:
tiinasPreferences.language<console>:20: error: value language is not a member of Some[Preferences] tiinasPreferences.language ^
Näin helpolla emme sentään pääse, koska asetustiedot voivat puuttua tyystin. Some
-oliolla
ei ole language
-ilmentymämuuttujaa, mutta sen sisällöllä on. Aivan kuin aiemmassa
lukuesimerkissä, käytetään nytkin map
-metodia:
tiinasPreferences.map( _.language )res21: Option[Option[String]] = Some(Some(Finnish))
Option[Option[String]]
.tiinasPreferences
ei ole
None
vaan Some
-olioon kääritty Preferences
-olio.
Tämän Preferences
-olion language
ei sekään ole None
vaan Some
-olioon kääritty merkkijono "Finnish"
.
Kun map
-metodilla pyydetään Preferences
-olion
language
-muuttujan arvo, saadaan siis sisäkkäiset
Some
-oliot.flatten
-metodi poistaa sisäkkäisyyden:
tiinasPreferences.map( _.language ).flattenres22: Option[String] = Some(Finnish)
Toivottavasti jo tuttuun tapaan map
ja flatten
voidaan yhdistää flatMap
-kutsuksi.
tiinasPreferences.flatMap( _.language )res23: Option[String] = Some(Finnish)
Näin siis saimme selville Tiinan kieliasetuksen Option[String]
-muodossa: Some(Finnish)
tarkoittaa, että kieliasetus on olemassa ja se on suomi.
Tavoitteenamme oli käyttää käyttäjän asetusta, jos sellainen on, ja englantia muutoin.
getOrElse
-metodi sopii tarkoitukseen:
val tiinasLanguage = tiinasPreferences.flatMap( _.language ).getOrElse("English")tiinasLanguage: String = Finnish
Tuloksena saatiin Option
iin käärimätön merkkijono, joka kertoo valitun kielen.
Ratkaisu kootusti
Abstrahoidaan äskeisestä tapauksesta funktio, joka selvittää annettujen (mahdollisesti puuttuvien) asetustietojen perusteella, mitä kieltä tulisi käyttää:
def chooseLanguage(prefs: Option[Preferences]) = prefs.flatMap( _.language ).getOrElse("English")chooseLanguage: (prefs: Option[Preferences])String
Tässä vielä kootusti kaikkien kolmen testihenkilömme asetukset ja käskyt, joilla kullekin heistä määritetään käytettävä kieli:
val tiinasPreferences = Some(new Preferences("Tiina's profile", Some("Finnish"), Some(true)))tiinasPreferences: Some[Preferences] = Some(lang: Finnish, metric: true) val tainasPreferences = Some(new Preferences("Taina's profile", None, Some(true)))tainasPreferences: Some[Preferences] = Some(lang: NOT SET, metric: true) val teemusPreferences: Option[Preferences] = NoneteemusPreferences: Option[Preferences] = None val tiinasLanguage = chooseLanguage(tiinasPreferences)tiinasLanguage: String = Finnish val tainasLanguage = chooseLanguage(tainasPreferences)tainasLanguage: String = English val teemusLanguage = chooseLanguage(teemusPreferences)teemusLanguage: String = English
Lisäesimerkki
Jos haluat, voit katsoa myös seuraavan toString
-toteutuksen.
Siinä on lisäesimerkki map
-metodin käytöstä Option
-olioille.
class Preferences(val profileName: String,
val language: Option[String],
val metricSystem: Option[Boolean]) {
override def toString = {
def describe(name: String, value: Option[String]) = name + ": " + value.getOrElse("NOT SET")
describe("lang", this.language) + ", " + describe("metric", this.metricSystem.map( _.toString ))
}
}
Olisi tuo match
illakin mennyt mutta monimutkaisemmin
Esimerkiksi näin:
def chooseLanguage(prefs: Option[Preferences]) = {
val languagePref = prefs match {
case Some(existingPrefs) => existingPrefs.language
case None => None
}
languagePref match {
case Some(pref) => pref
case None => "English"
}
}
Passenger
-esimerkki
Luvussa 4.4 teit luokan Passenger
luokkaa TravelCard
apuna käyttäen. Työvälineenä oli
match
-käsky, ja canTravel
-metodin ratkaisu näytti tältä:
class Passenger(val name: String, val card: Option[TravelCard]) {
def canTravel = this.card match {
case Some(actualCard) => actualCard.isValid
case None => false
}
}
Nyt tiedämme, että saman saa tehtyä näinkin:
class Passenger(val name: String, val card: Option[TravelCard]) {
def canTravel = this.card.exists( _.isValid )
}
Yleisemmin voimme sanoa, että seuraavat koodit ajavat saman asian.
x match {
case Some(sisalto) => jokuEhto(sisalto)
case None => false
}
x.exists(jokuEhto)
x
viittaa tässä johonkin Option
-olioon.jokuEhto
on jokin toimenpide, joka tuottaa
Boolean
-arvon Option
-olion sisällön perusteella.Muitakin Option
in metodeita kuin exists
voi käyttää match
-käskyn sijasta.
Tilaisuuksia tehdä niin tarjoutuu esimerkiksi robottiprojektissa, johon nyt palaamme.
Robottien liikettä
Tehtävän kaksi ensimmäistä vaihetta teit luvussa 8.1. Seuraavat kaksi tehdään nyt.
Yleisiä vinkkejä kaikkiin robottisimulaattorimme tuleviin vaiheisiin:
Option
on alkiokokoelma".
Se auttoi kovasti.Option
ien exists
-, forall
- ja
foreach
-toiminnot ovat aivan ihania.Koeta ratkaista tämän ja seuraavan luvun tehtävät käyttämättä match
-käskyä Option
eiden
käsittelyyn. Käytä sen sijaan esiteltyjä korkeamman asteen metodeita. (OK, jos ei meinaa
millään onnistua, niin saat kyllä käyttää match
iäkin.)
Robottitehtävä, vaihe 3/8: toimintavuorot ja Spinbot
- Toteuta tämän luvun alussa esiin nostettu metodi
takeTurn
luokkaanRobotBody
. Se käy yksinkertaisesti, kunhan valitset sopivan välineenOption
in käsittelyyn. RobotBody
-luokasta puuttuu myös metodispinClockwise
. Toteuta se.Spinbot
-luokanmoveBody
-metodi ei tee mitään. Täydennä se.RobotWorld
-luokasta puuttuvat metoditnextRobot
,advanceTurn
jaadvanceFullTurn
. Täydennä ne. Korkeamman asteen metodeista voi tässäkin olla apua.- Varmista kokeilemalla, että
Spinbot
it toimivat nyt. Kokeile myös rikkoa robotti käyttöliittymän kautta ja varmista, että kaikki silti toimii eli robotti ei.
Robottitehtävä, vaihe 4/8: liikkumisen rajoitukset
RobotBody
-luokasta puuttuu robottien ympäristöön liittyviä metoditoteutuksia.
- Toteuta
RobotBody
n metoditneighboringSquare
,canMoveTowards
jaisStuck
. - Palauta ratkaisusi vaiheisiin 3 ja 4 tällä lomakkeella.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Jatkamme robottiprojektia seuraavassa luvussa 8.3. Ensin kokoamme yhteen Option
-tyypistä
opittua.
Pieniä mutta pähkinäisiä
Mitä opittiin?
Erittäin moni Option
-olioihin kohdistuva toimenpide on kätevästi toteutettavissa
kutsumalla jotakin Option
-olion metodia. Korkeamman asteen metodeita käyttämällä
koodista tulee tiivistä — mutta ymmärrettävää, kunhan lukija tuntee kyseiset yleisessä
käytössä olevat metodit.
Voit itse harkita, kuinka paljon haluat käyttää valintakäskyjä kuten if
ja match
, ja
kuinka paljon korkeamman asteen metodeita, mutta molemmat toteutustavat kannattaa tuntea.
Mielessä kannattaa pitää myös mahdollisuus käsitellä Option
-oliota for
-silmukalla
kuten yllä olevan monivalintatehtävän viimeisessä kohdassa.
Millainen roboratkaisu tuli?
Jos et vielä käyttänyt tämän luvun esittelemiä metodeita
robottiohjelmassasi, tee se nyt; se on opettavaista.
Selviytyisitkö kokonaan ilman match
-käskyjä?
Lisää vastaavia monivalintoja
Jos edellinen ei piisannut, voit treenata Option
-ajatteluasi myös seuraavassa
tehtävässä, jossa samaan tapaan pyydetään valitsemaan annettua koodia vastaava
korkeamman asteen metodi.
Seuraavissa koodinpätkissä on käytetty Option
-olion get
-metodia, joka
yksinkertaisesti ottaa arvon kääreen sisältä ja kaatuu ajonaikaiseen virheeseen,
jos sitä kutsuu None
-oliolle. Kuten luvun 4.3 lopun vapaaehtoisessa
materiaalissa kerrottiin, get
-metodin käyttöä sopisi kaihtaa, koska se kerjää
huolimattomuusvirhettä. Tämäkään tehtävä ei pyri esittelemään get
-metodia
hyvänä vaihtoehtona vaan päinvastoin osoittamaan, että sen, mitä get
illä voi
tehdä, saa tehtyä muutenkin.
Lisää pohdittavaa ja lukemista
Mieti, voisiko Option
-luokan käytön sijaan käyttää puskuria,
johon aina lisäisi korkeintaan yhden alkion. Mitä hyötyä tai
haittaa tästä olisi?
Selvitä, millainen on Scalan Either
-luokka ja miten sitä voi
käyttää osin samoihin tarkoituksiin kuin Option
ia. Selvitä myös
mitä ovat toisiinsa liittyvät luokat Try
, Success
ja Failure
.
Voit lukea, mitä on sanottu
Option
-luokan mahdollisesta liikakäytöstä. Löydätkö tästä luvusta
esimerkkejä, joiden koodia voisi selkiyttää toisenlaisella
ratkaisulla? Siitäkin voit lukea, mikä on Null Object
-suunnittelumalli
ja miten se liittyy samaan teemaan.
Yhteenvetoa
Option
-oliokin on alkiokokoelma. Se sisältää nolla tai yksi alkiota.Option
-oliolla on muista kokoelmista tuttuja metodeita:foreach
,exists
,map
,flatMap
jne. Niiden avulla "ehkä olemassa olevia arvoja" on helpompi käsitellä ohjelmissa.match
-käskykin toki toimii, mutta nämä metodit ovat usein näppärämpiä, kunhan totut niihin.
- Myös
for
-silmukkaa voi käyttääOption
-kokoelman läpikäymiseen. - Lukuun liittyviä termejä sanastosivulla:
Option
, kokoelma, korkeamman asteen funktio.
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.
Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.
Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.