povídky foto kultura ostatní stripy
facebook twitter
ASCII blog doomsday party

k47.cz

9. 2. 2012

Scala - String interpolation

       

String interpolation (SIP-11) je jedna z mnoha novinek jazyka Scala plánovaných pro verzi 2.10. Je to vlastnost typická pro dynamické jazyky, kdy můžete přímo do stringového literálu uvést proměnné, které jsou následně interpolovány, ale v podání Scaly je maximálně zobecněná, aby dala programátorům co největší moc.


String interpolation si můžete vyzkoušet už teď, když si stáhnete nejnovější nightly build a použijete přepínač `-Xexperimental`.

Ve Scale se (stejně jako v Javě) stringy spojují pomocí operátoru `+`. Konvence Scaly je kolem plusů nedělat mezery.

"Bob is "+n+" years old"

To všechno funguje, ale když spojujete fůru stringů, začne být situace velice nepřehledná + může být celkem těžké v tom zmatku správně napsat mezery, uvozovky nebo závorky.

SIP-11 zavádí novou syntax pro tzv. processed string:

s"Bob is $n years old"

- `s` na začátku oznamuje, že se jedná o interpolovaný string
- `$` uvozuje jméno proměnné

Funguje to i pro víceřádkové stringy:

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

Ve složených závorkách můžeme uvést libovolný výraz:

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

Obsah ve složených závorkách spadá do syntaktické kategorie `BlockExpr`, takže tam může být skutečně cokoli. Klidně můžete dovnitř stringu schovat celý váš program.

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 neznamená, že byste měli.

Další typ řetězce je formátovaný string uvozený `f`, ve kterém můžete za proměnnou/výrazem specifikovat formát používaný v `printf`. Proměnná se pak vypíše v uvedeném formá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"

Skutečná síla se ale skrývá v tom, jak se interpolované stringy překládají. Nejjednodušší by bylo prostě mít jednu nebo dvě nové formy syntaxe, které se přímo přeloží na string (jak je běžné v dynamických jazycích), ale to by nebylo příliš flexibilní.

String `s"Bob is ${bob.years} years old"` se přeloží jako:

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

Všechny konstantní části stringu jsou předány objektu `StringContext`, identifikátor stringu `s` je pak jenom obyčejná metoda, které jsou všechny interpolované proměnné předány jako vararg. Pokud tedy chcete vlastní druh stringu, stačí vytvořit vlastní StringContext, který má požadované metody a implicitní konverzi, která zkonvertuje základní StringContext na ten váš.

A aby to byl ještě o něco mocnější nástroj, můžeme na interpolovaných řetězcích dělat patter matching. Musíte mít `StringContext`, jehož metoda `id` vrátí objekt, který funguje jako extraktor.

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

Nejlepší na tom všem je, že interpolovaný string se chová jako jiný jazyk vložený do scalovského kódu, kde proměnné mají jiný význam než v kódu okolo. Podobá se to pojetí Perlu 6 nebo plánovaným Quasi-Literalům z ECMA Scriptu.

Možná použití: makra

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

Můžeme tak nahradit nativní podporu XML.

xml"""
  
     ${linktext.take(100)} 
  
"""

Když vezmeme v potaz plánovaná makra a metoda `xml` nebude obyčejná metoda, ale makro metoda, můžeme během kompilace provést validaci xml.

Další použití může být v regulárních výrazech:

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

Nebo pro konstrukci sql dotazů:

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

`sql` může provést kontextové escapování, `table` bude uvozeno zpětnými uvozovkami, `name` bude uvozen jako sql string dvojitými uvozovkami. Výsledkem přitom vůbec nemusí být řetězec, ale objekt reprezentující databázový dotaz, výsledek nebo iterátor řádků.

Další možné použití je pro lokalizaci textů:

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

Typicky se pro lokalizaci používá nějaká funkce, která má jako první argument překládaný string, která má čísla nahrazena nějakým placeholderem a další argumenty jsou čísla, podle kterých se použije jednotné nebo množné číslo:

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

Interpolovaný string se přeloží na něco velice podobné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ě mocnější ve spojení s makry.


příbuzné články:
Scala - Booleovská kompozice funkcí
Scala - konverze TupleN na case class
Conway's game of life
Scala - líné parametry, líné závislosti a líné proxy
Sleeping patterns