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 - String interpolation

9. 2. 2012 — k47 (CC by-nc-sa) (♪)

String in­ter­po­lation (SIP-11) je jedna z mnoha no­vi­nek jazyka Scala plá­no­va­ných pro verzi 2.10. Je to vlast­nost ty­pická pro dy­na­mické jazyky, kdy můžete přímo do strin­go­vého li­te­rálu uvést pro­měnné, které jsou ná­sledně in­ter­po­lo­vány, ale v podání Scaly je ma­xi­málně zo­bec­něná, aby dala pro­gra­má­to­rům co nej­větší moc.


String in­ter­po­lation si můžete vy­zkou­šet už teď, když si stáh­nete nej­no­vější ni­ghtly build a po­u­ži­jete pře­pí­nač -Xexperimental.

Ve Scale se (stejně jako v Javě) stringy spo­jují pomocí ope­rá­toru +. Kon­vence Scaly je kolem plusů ne­dě­lat mezery.

"Bob is "+n+" years old"

To všechno fun­guje, ale když spo­ju­jete fůru stringů, začne být si­tu­ace velice ne­pře­hledná + může být celkem těžké v tom zmatku správně napsat mezery, uvo­zovky nebo zá­vorky.

SIP-11 zavádí novou syntax pro tzv. pro­ces­sed string:

s"Bob is $n years old"

Fun­guje to i pro ví­ce­řád­kové stringy:

s"""
  Bob
  is
  $n
  years
  old
"""

Ve slo­že­ných zá­vor­kách můžeme uvést li­bo­volný výraz:

s"Bob is ${bob.years} years old"

Obsah ve slo­že­ných zá­vor­kách spadá do syn­tak­tické ka­te­go­rie BlockExpr, takže tam může být sku­tečně cokoli. Klidně můžete dovnitř stringu scho­vat celý váš pro­gram.

s"""Move along. There's nothing to see. ${ class MyHiddenClass() } Did you see something? I don't think so!"""

Ale však to znáte: proto, že můžete ne­zna­mená, že byste měli.

Další typ ře­tězce je for­má­to­vaný string uvo­zený f, ve kterém můžete za pro­měn­nou/vý­ra­zem spe­ci­fi­ko­vat formát po­u­ží­vaný v printf. Pro­měnná se pak vypíše v uve­de­ném for­mátu.

val c = 7
f"His codename is $c%03d" // proměnná $c, formátovací string: %03d, vrátí: "His codename is 007"
f"His codename is ${secretAgent.codename}%03d"

Sku­tečná síla se ale skrývá v tom, jak se in­ter­po­lo­vané stringy pře­klá­dají. Nej­jed­no­dušší by bylo prostě mít jednu nebo dvě nové formy syn­taxe, které se přímo pře­loží na string (jak je běžné v dy­na­mic­kých ja­zy­cích), ale to by nebylo příliš fle­xi­bilní.

String s"Bob is ${bob.years} years old" se pře­loží jako:

StringContext("Bob is ", " years old").s(bob.years)

Všechny kon­stantní části stringu jsou pře­dány ob­jektu StringContext, iden­ti­fi­ká­tor stringu s je pak jenom oby­čejná metoda, které jsou všechny in­ter­po­lo­vané pro­měnné pře­dány jako vararg. Pokud tedy chcete vlastní druh stringu, stačí vy­tvo­řit vlastní Strin­g­Con­text, který má po­ža­do­vané metody a im­pli­citní kon­verzi, která zkon­ver­tuje zá­kladní Strin­g­Con­text na ten váš.

A aby to byl ještě o něco moc­nější ná­stroj, můžeme na in­ter­po­lo­va­ných ře­těz­cích dělat patter matching. Musíte mít StringContext, jehož metoda id vrátí objekt, který fun­guje jako ex­trak­tor.

val id"""matching to variable $a and $b""" = x

Nej­lepší na tom všem je, že in­ter­po­lo­vaný string se chová jako jiný jazyk vlo­žený do sca­lov­ského kódu, kde pro­měnné mají jiný význam než v kódu okolo. Podobá se to pojetí Perlu 6 nebo plá­no­va­ným Quasi-Li­te­ra­lům z ECMA Scriptu.

Možná po­u­žití: makra

macro def atomic[A](expr: A): A = scala"""
  scala.concurrent.transaction.withinTransaction {
    (implicit currentTransaction: Transaction) =>
      $expr
}
"""

Můžeme tak na­hra­dit na­tivní pod­poru XML.

xml"""
  <body>
    <a href="url"> ${linktext.take(100)} </a>
  </body>
"""

Když vez­meme v potaz plá­no­vaná makra a metoda xml nebude oby­čejná metoda, ale makro metoda, můžeme během kom­pi­lace pro­vést va­li­daci xml.

Další po­u­žití může být v re­gu­lár­ních vý­ra­zech:

val regex"^($name\w+)\s*=\s*($value\w+)$" = "MainAntagonist = SHODAN"

Nebo pro kon­strukci sql dotazů:

sql"select * from $table where name = $name"

sql může pro­vést kon­tex­tové es­ca­po­vání, table bude uvo­zeno zpět­nými uvo­zov­kami, name bude uvozen jako sql string dvo­ji­tými uvo­zov­kami. Vý­sled­kem přitom vůbec nemusí být ře­tě­zec, ale objekt re­pre­zen­tu­jící da­ta­bá­zový dotaz, vý­sle­dek nebo ite­rá­tor řádků.

Další možné po­u­žití je pro lo­ka­li­zaci textů:

l10n"""$n bear bottles standing on the wall"""

Ty­picky se pro lo­ka­li­zaci po­u­žívá nějaká funkce, která má jako první ar­gu­ment pře­klá­daný string, která má čísla na­hra­zena ně­ja­kým pla­ce­hol­de­rem a další ar­gu­menty jsou čísla, podle kte­rých se po­u­žije jed­notné nebo množné číslo:

translate("%d bear bottles standing on the wall", n)

In­ter­po­lo­vaný string se pře­loží na něco velice po­dob­ného:

L10NStringContext("", "bear bottles standing on the wall").l10n(n)

Takže ve vý­sledku Scala bude mít další mocný ná­stroj, který bude ještě moc­nější ve spo­jení s makry.

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