About correct typing (in the presence of builtin functions)


Builtin functions have the special property of implementing
'templates'.
Take for example 'foreach', which iterates over any kind
of lists, e.g.

  list l = ["a", "b", "c", 0 ];
  string lc = "";
  foreach (string s, l, { lc = lc + s; });

The above code concatenates all elements of 'l' to 'lc'

At first sight, the code looks quite correct -- syntactically.
(and yast2-core from preview4 will happily accept the
code and probably crash ...)

Looking at the typing information reveals a gap in the
parameters for 'foreach()':

'foreach()' assigns every element of the list in turn to
the variable which is passed as first parameter ('string s'
in the above example.

However, the type information of 'l' (declared as 'list'
which equals to 'list<any>') does not guarantee that every
element of 'l' is a string. And indeed, the last element
of 'l' is an integer.

So 'foreach()' must check that the type of the variable
is correct for each element of the list.

The same applies to all other builtin functions which
operate on lists and maps


As of version 2.9.51, yast2-core (ycpc) will implement
a stricter type checking which will detect the above
typing errors.


The question now is, how should the correct code look
like ?

There are two solutions:

Solution 1 (preferred)

  Clearly type the list in order to detect errors during
  parse-time.

The intention of the above code is 'string concatenation',
so 'l' should be a list of strings and declared as such:

  list<string> l = ["a", "b", "c", 0 ];
  string lc = "";
  foreach (string s, l, { lc = lc + s; });

Here the type checker will flag the initial assignment
of 'l' as an error since the list value is not of
type 'list<string>' but of 'list' (meaning 'list<any>')


Solution 2

  Check the types at runtime

If your list (or map value) mixes different types, you
must implement runtime type checking (with 'is()') and
type conversion (with casts or conversion functions).

The function 'is ( value, type)' returns 'true' if 'value'
is of type 'type'.

Using a cast:

  list l = ["a", "b", "c", 0 ];
  string lc = "";
  foreach (any a, l,			// <<-- 'any a' here 
  {
    if (!is (a, string)) continue;

    lc = lc + (string)a;
  });

This will filter out all non-string elements of l.
The cast will execute a type check and conversion at runtime.

If the cast cannot convert a value to the desired type, 'nil'
is returned (!).
The call to 'is()' above ensures that the value of 'a'
is a string and the conversion will succeed.


Using a conversion function:

If you want to get all elements regardless of their type,
using a conversion function might be the right solution

  list l = ["a", "b", "c", 0 ];
  string lc = "";
  foreach (any a, l,			// <<-- 'any a' here 
  {
    if (is (a, string))
    {
      lc = lc + (string)a;
    }
    else
    {
      lc = lc + tostring (a);
    }
  });

