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

Scala obsahuje šikovný modifikátor lazy
který z obyčejné val
proměnné nebo
členské proměnné udělá její línou variantu. Ta se vyhodnotí
na prvním místě, kde je zmíněna a pak takto vypočtenou hodnotu 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 potřeboval líné parametry funkcí, metod a konstruktorů,
ale zápis def func(lazy x: SomeType)
není ve Scale validní.
Naštěstí není nic ztraceno, protože líné parametry můžeme vytvořit kombinací líných fieldů a by-name argumentů. 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 zavolána na místě by-name
argumentu. To zajistí, že místo vyhodnocení bude „zabalena“ do funkce, která
bude vyhodnocena až v těle shootHimToTheMoon
a to na každém místě, kde je
zmíněna, tedy klidně několikrát nebo také ani jednou.
def test(arg: => Int) = { arg; arg; arg } test { println("side effect!"); 1 } // vypíše 3x "side effect"
Ale protože je ve funkci shootHimToTheMoon
by-name argument hned přiřazen do
lazy val
a ta je vyhodnocena až při prvním použití, je argument také
vyhodnocen jenom jednou nebo vůbec. A to je efektivně líný argument.
Toho se dá snadno využít pro předávání líných závislosti do tříd (jak se to řešilo tady nebo tady):
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 podobně můžeme konstruovat líné proxy (jako v odkazovaném Augiho článku, který tohle moje snažení nastartoval):
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 vytvoříme jednoduše:
val lazyLoginProxy = new LoginServiceLazyProxy(new LoginService)
Pořád platí, že instance LoginService se vytvoří jenom když bude skutečně potřeba.