Oppimateriaalin copyright © 2011 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.
(Muutettu viimeksi 31.3.2011)

Matala ja syvä sidonta

Shallow and deep binding

Kysymys on, mihin viitausympäristöön parametrina välitetty aliohjelma/funktio liitetään. Ilmiö esiintyy myös kielissä, joissa näkyvyysalueet määräytyvät staattisesti ohjelman kirjoitusasusta eli "leksikaalisesti". Tilanne syntyy rekursion yhteydessä.

Tarkastellaan seuraavaa esimerkkiä. (Pyrin kirjoittamaan yksityiskohdat hieman "javamaisesti" (tai "ceemäisesti"), mutta Javasta poiketen tässä metodi voi sisältää metodeita ja oletusnäkyvyys on "sisältä ulos" Algolin ja Pascalin tapaan. Ja myös metodit/funktiot kelpaavat parametreiksi.)

//--------------------------------------------------------------------------
program Esimerkki {

  procedure A(i:int; P():procedure) {

     procedure B() {  // A:n sisäinen aliohjelma, so. A:ssa määritelty aliohjelma
       print(i);   // tämän i:n merkitys on se kiinnostava kohta
     }

     // A:n algoritmi (so. kun A:ta kutsutaan, tämä käynnistyy)
     if (i > 1)
       P();      // A:n muodollisen proseduuriparametrin kutsu!
     else
       A(2, B);  // B annetaan todellisena parametrina rekursiivisessa kutsussa
  }  // A:n määrittely päättyy

  procedure C() {
    // C on tyhjä proseduuri, A:n kanssa rinnakkainen
  }

  // pääohjelma
  A(1, C); // C annetaan todellisena parametrina
}
//--------------------------------------------------------------------------
Tässä esimerkissä kyse on siitä, mihin A:n muodollisen parametrin i versioon B:ssä viitataan, kun A on kutsunut itseään rekursiivisesti. A:sta on kaksi aktivointia, jotka molemmat ovat määritelleet i:n, ja siis molemmilla on oma i.

Tässä luki näin aina 31.3.2011 asti:
"Syvässä sidonnassa (deep binding) käytetään A:n ensimmäisen aktivoinnin i:tä, matalassa sidonnassa (shallow binding) viimeisimmän aktivoinnin i:tä". Tämä oli vähintääkin epämääräisesti sanottu, ellei peräti väärin... Tosin näinkin voisi kielen toteuttaa...

Normaalisti syvä sidonta tarkoittaa sitä, että kun funktio annetaan todellisena funktioparametrina – "elaboroidaan" – funktio sidotaan kyseisen kutsukohdan viittausympäristöön. Eli tässäkin tapauksessa on perusteltua puhua sulkeumasta.

Äskeinen esimerkki voidaan ohjelmoida Scalalla:

//--------------------------------------------------------------------------
def A(i:Int, P:Unit):Unit = {  // P on muodollinen funktioparametri

  def B() = {  // A:n sisäinen aliohjelma
    println(i) // tämän i:n merkitys on se kiinnostava kohta:
  };           // sidotaanko i ensimmäisessä vai toisessa (sisemmässä) A:n kutsussa

  // A:n algoritmi (so. A:n "pääohjelma"):
  if (i > 1)
    P        // muodollisen funktioparametrin kutsu!
  else
    A(2, B)  // funktio B annetaan todellisena parametrina rekursiivisessa kutsussa
}  // A:n määrittely päättyy

def C() = {} // C on tyhjä funktio, A:n kanssa rinnakkainen

// pääohjelma:
A(1, C) // funktio C annetaan todellisena parametrina
//--------------------------------------------------------------------------
Kuten kokeilun seurauksena voidaan nähdä, Scalassa sidonta on syvä. Tämä esimerkki ei kuitenkaan havainnollistanut koko totuutta!

Edellä kerrottua virheellistä(?) selitystäni ja sen korjausta sen sijaan havainnollistaa kurssin opiskelijan Heikki Aitakankaan 29.3.2011 antama tyylikäs esimerkki:

//-----------------------------
def A(i:Int, P:Unit):Unit = {
  def B() = { println(i) };

  if(i == 1)
    A(2, P)
  else if(i == 2)
    A(3, B)
  else
    P
}

def C() = {}

A(1, C) 
//---- tulostaa luvun 2 ------

Ohjelmointikielten tutkija Juha Vihavainen kehitteli vielä hieman edellistä 30.3.2011:

//------------------------------------
def Q (P: Unit) : Unit = { P }

def A(i:Int, P:Unit):Unit = {
   def B() = { println(i) };

   if(i == 1) {            
     A(2, P); Q (B);
   }
   else if(i == 2) {
     A(3, B);  Q (B);
   }
   else {
     P; Q (B);
   }
}

def C() = {}

A(1, C)
//---- tulostaa luvut 2, 3, 2 ja 1 ---

Näitä esimerkkejä kannattaa simuloida aktivaatiotietuepinoa piirrellen. On syytä olla tarkkana erityisesti parametrivälityksen kanssa!

Olen muuten sitä mieltä, että "shallow" sidonta aidosti lohkorakenteisissa staattisen sidonnan kielissä vaikuttaa täysin käsitämättömältä idealta...

Loppuhuipennukseksi vielä Scottin selitystä (3rd edition, Ch. 3.6.1 Subroutine closures, s.153-154):

"Deep binding is implemented by creating an explicit representation of referencing environment (generally the one in which the subroutine would execute if called at the present time) and bundling it together with a reference to the subroutine. This bundle as a whole is referred to as a closure."

"A running program may have more than one instance of an object that is declared within a recursive subroutine. A closure in a language with static scoping captures the current instance of every object, at the time the closure is created. When the closure's subroutine is called, it will find these captured instances, even if newer instances have subsequently been created by recursive calls."

Linkkivinkki Wikipwdia-artikkeliin: Funarg problem


Takaisin sisältösivulle.