k47.cz  — každý den dokud se vám to nezačne líbit
foto Praha výběr povídky kultura
twitter FB RSS

Scala - líné parametry, líné závislosti a líné proxy

24. 9. 2012 — k47 (CC by)

Scala ob­sa­huje ši­kovný mo­di­fi­ká­tor lazy který z oby­čejné val pro­měnné nebo člen­ské pro­měnné udělá její línou va­ri­antu. Ta se vy­hod­notí na prvním místě, kde je zmí­něna a pak takto vy­poč­te­nou hod­notu jenom recykluje.


class Person {
  // final field, stable, computed on object creation
  val dateOfBirth: Date = ???

  // lazy field, stable, computed on first call
  lazy val allPredecessorsUpToApes: Seq[Person] = ???

  // method, not stable, recomputed on every call
  def age: Int = ???

  // truly variable variable
  var name: String = ???

  def test = {
    // lazy local field
    lazy val localLazyVariable = ???
  }
}

Někdy ale může nastat případ, kdy by člověk po­tře­bo­val líné pa­ra­me­try funkcí, metod a kon­struk­torů, ale zápis def func(lazy x: SomeType) není ve Scale va­lidní.

Na­štěstí není nic ztra­ceno, pro­tože líné pa­ra­me­try můžeme vy­tvo­řit kom­bi­nací líných fieldů a by-name ar­gu­mentů. Takhle:

def shootHimToTheMoon(byNamePerson: => Person) {
  lazy val lazyPerson = byNamePerson

  lazyPerson // tady se vyhodnotí
  lazyPerson // tady se použije předchozí hodnota

  // ...
}

shootHimToTheMoon(expensiveCreationOfPersonObject())

Funkce expensiveCreationOfPersonObject je za­vo­lána na místě by-name ar­gu­mentu. To za­jistí, že místo vy­hod­no­cení bude „za­ba­lena“ do funkce, která bude vy­hod­no­cena až v těle shootHimToTheMoon a to na každém místě, kde je zmí­něna, tedy klidně ně­ko­li­krát nebo také ani jednou.

def test(arg: => Int) = { arg; arg; arg }
test { println("side effect!"); 1 } // vypíše 3x "side effect"

Ale pro­tože je ve funkci shootHimToTheMoon by-name ar­gu­ment hned při­řa­zen do lazy val a ta je vy­hod­no­cena až při prvním po­u­žití, je ar­gu­ment také vy­hod­no­cen jenom jednou nebo vůbec. A to je efek­tivně líný ar­gu­ment.


Toho se dá snadno využít pro pře­dá­vání líných zá­vis­losti do tříd (jak už to řešil Augi, David nebo jiní):

class HomeController(_loginService: => ILoginService) extends Controller {
  lazy val loginService = _loginService

  def showLogin() = {
    // nepotřebuje loginService, takže se líná hodnota vůbec nebude materializovat
    PartialView()
  }

  def processLogin(credentials: Credentials) = {
    // tady už potřebuje loginService, tak ho použije
    // použití a sémantika lazy val je stejná jako v případě val
    loginService.login(credentials)

    // ... and now for something completely different
  }
}

Velice po­dobně můžeme kon­stru­o­vat líné proxy (jako v od­ka­zo­va­ném Augiho článku, který tohle moje sna­žení na­star­to­val):

class LoginServiceLazyProxy(accessor: => ILoginService) extends ILoginService {
  lazy val live = accessor // thread safety included

  def login(credentials: Credentials): LoginResult =
    live.login(credentials)
}

Línou proxy pak vy­tvo­říme jed­no­duše:

val lazyLoginProxy = new LoginServiceLazyProxy(new LoginService)

Pořád platí, že in­stance Lo­gin­Ser­vice se vy­tvoří jenom když bude sku­tečně po­třeba.

píše k47 & hosté, ascii@k47.cz