============== Abstract types ============== .. contents:: Abstract types in Nimrod provide a means to model different `units`:idx: of a `base type`:idx:. Use case 1: SQL strings ----------------------- An SQL statement that is passed from Nimrod to an SQL database might be modelled as a string. However, using string templates and filling in the values is vulnerable to the famous `SQL injection attack`:idx:\: .. code-block:: nimrod proc query(db: TDbHandle, statement: TSQL) = ... var username: string db.query("SELECT FROM users WHERE name = '$1'" % username) # Horrible security hole, but the compiler does not mind! This can be avoided by distinguishing strings that contain SQL from strings that don't. Abstract types provide a means to introduce a new string type ``TSQL`` that is incompatible with ``string``: .. code-block:: nimrod type TSQL = abstract string proc query(db: TDbHandle, statement: TSQL) = ... var username: string db.query("SELECT FROM users WHERE name = '$1'" % username) # Error at compile time: `query` expects an SQL string! It is an essential property of abstract types that they **do not** imply a subtype relation between the abtract type and its base type. Explict type conversions from ``string`` to ``TSQL`` are allowed: .. code-block:: nimrod proc properQuote(s: string): TSQL = # quotes a string properly for an SQL statement ... proc `%` (frmt: TSQL, values: openarray[string]): TSQL = # quote each argument: var v = values.each(properQuote) # we need a temporary type for the type conversion :-( type TStrSeq = seq[string] # call strutils.`%`: result = TSQL(string(frmt) % TStrSeq(v)) db.query("SELECT FROM users WHERE name = $1".TSQL % username) Now we have compile-time checking against SQL injection attacks. Since ``"".TSQL`` is transformed to ``TSQL("")`` no new syntax is needed for nice looking ``TSQL`` string literals. Use case 2: Money ----------------- Different currencies should not be mixed in monetary calculations. Abstract types are a perfect tool to model different currencies: .. code-block:: nimrod type TDollar = abstract int TEuro = abstract int var d: TDollar e: TEuro echo d + 12 # Error: cannot add a number with no unit with a ``TDollar`` Unfortunetaly, ``d + 12.TDollar`` is not allowed either, because ``+`` is defined for ``int`` (among others), not for ``TDollar``. So we define our own ``+`` for dollars: .. code-block:: proc `+` (x, y: TDollar): TDollar = result = TDollar(int(x) + int(y)) It does not make sense to multiply a dollar with a dollar, but with a number without unit; and the same holds for division: .. code-block:: proc `*` (x: TDollar, y: int): TDollar = result = TDollar(int(x) * y) proc `*` (x: int, y: TDollar): TDollar = result = TDollar(x * int(y)) proc `div` ... This quickly gets tedious. The implementations are trivial and the compiler should not generate all this code only to optimize it away later - after all ``+`` for dollars should produce the same binary code as ``+`` for ints. The pragma ``borrow`` has been designed to solve this problem; in principle it generates the trivial implementation for us: .. code-block:: nimrod proc `*` (x: TDollar, y: int): TDollar {.borrow.} proc `*` (x: int, y: TDollar): TDollar {.borrow.} proc `div` (x: TDollar, y: int): TDollar {.borrow.} The ``borrow`` pragma makes the compiler to use the same implementation as the proc that deals with the abstract type's base type, so no code is generated. But it seems we still have to repeat all this boilerplate code for the ``TEuro`` currency. Fortunately, Nimrod has a template mechanism: .. code-block:: nimrod template Additive(typ: typeDesc): stmt = proc `+` *(x, y: typ): typ {.borrow.} proc `-` *(x, y: typ): typ {.borrow.} # unary operators: proc `+` *(x: typ): typ {.borrow.} proc `-` *(x: typ): typ {.borrow.} template Multiplicative(typ, base: typeDesc): stmt = proc `*` *(x: typ, y: base): typ {.borrow.} proc `*` *(x: base, y: typ): typ {.borrow.} proc `div` *(x: typ, y: base): typ {.borrow.} proc `mod` *(x: typ, y: base): typ {.borrow.} template Comparable(typ: typeDesc): stmt = proc `<` * (x, y: typ): bool {.borrow.} proc `<=` * (x, y: typ): bool {.borrow.} proc `==` * (x, y: typ): bool {.borrow.} template DefineCurrency(typ, base: expr): stmt = type typ* = abstract base Additive(typ) Multiplicative(typ, base) Comparable(typ) DefineCurrency(TDollar, int) DefineCurrency(TEuro, int)