2
$\begingroup$

I'm trying to implement a templating system in Mathematica, a bit like the List<List<T>> you would find in C#.

Here's how it looks so far:

ClearAll[ListOf];
ListOf /: ListOf[ListOf[f_Symbol]] := Function[l, ListQ[l] && ListOf[f][First@l]];
ListOf[f_Symbol] := 
  Function[l, 
    ListQ[l] && (l == {} || Head[First[l]] === f || f[First[l]])] /; (f =!= ListOf);

My goal is to make it easier to spot what the arguments of a function look like:

f[l_?(ListOf[String])] := l;
f[l_?(ListOf[ListOf[String]])] := l;

However, multi-level definition won't work because ListOf[f] is replaced:

ListOf[Integer][{1, 2, 3}] (* True *)
ListOf[ListOf[Integer]][{{1, 2, 3}}] (* Inner ListOf[Integer] is evaluated *)

I've fiddled with HoldPattern and UpValues but I couldn't get it working.

Also please note the f[First[l]] is to also accept arguments such as ListOf[NumericQ].

$\endgroup$
1

1 Answer 1

4
$\begingroup$

General

I will show two (similar) ways of doing something like what you requested. Both are based on reusing the TypeSystem` package, which is built in V10+. This package has several predefined types - inert symbols to denote them, actually. In particular, it has Atom, Vector, Tuple, Assoc and Struct, with Atom and Vector immediately relevant for us.

Using TypeSystem`

We need to load the package first:

<<TypeSystem`

Now, we can do a few things. For example, this would be our test data:

dt = {{"a", "b", "c"}, {"d", "e", "f"}}
dtragged = {{"a", "b", "c"}, {"d", "e"}}

We can use the function DeduceType, to deduce the types of these:

t = DeduceType[dt]
tr = DeduceType[dtragged]

(*
  Vector[Vector[Atom[String], 3], 2]

  Vector[Vector[Atom[String], AnyLength], 2]
*)

We can also generate the type-validation function, using DataValidationFunction, like so:

vf = DataValidationFunction[t]

(* Length[#1] == 2 && MatrixQ[#1, StringQ[#1] ||  GeneralUtilities`MissingQ[#1] &] && Length[#1[[1]]] == 3 & *)

or

vfr = DataValidationFunction[tr]

(* ListQ[#1] && Length[#1] == 2 && VectorQ[#1, VectorQ[#1, StringQ[#1] || GeneralUtilities`MissingQ[#1] &] &] & *)

There are many other things one can do, but these are sufficient for our purposes.

Custom assignment operator using TypeSystem

We can now define a custom assignment operator, that would preprocess your definitions:

ClearAll[defTS];
SetAttributes[defTS, HoldAll];
defTS[(h : (Set | SetDelayed))[lhs_, rhs_]] :=
   h @@ Append[
      Hold[lhs] /. t : _Vector | _Atom :>
        With[{vf = DataValidationFunction[t]}, (_?vf) /; True], 
      Unevaluated[rhs]
   ]

It uses the Trott - Strzebonski technique to inject into held code. Here is an example:

defTS[ff[a : Vector[Vector[Atom[String], AnyLength], 2]] := Length[a]]

The resulting definition is then:

?ff
   Global`ff

   ff[a:_?(ListQ[#1]&&Length[#1]==2&&VectorQ[#1,VectorQ[#1,StringQ[#1]||GeneralUtilities`MissingQ[#1]&]&]&)]:=Length[a]

Compiling to patterns

We could also, instead of using the DataValidationFunction, reuse just the syntactic part of what TypeSystem` provides, and "compile" that to patterns, which are familiar and idiomatic way to test arguments. Here is a simple example:

ClearAll[compile];
compile[Atom[atomType_]] := _atomType;
compile[Vector[Atom[atomType_], AnyLength]] := {___atomType};
compile[Vector[type_, AnyLength]] := {compile[type] ...};
compile[Vector[type_, length_Integer]] := {Repeated[compile[type], length]};

For example:

compile[t]
compile[tr]

(*
   {Repeated[{Repeated[_String, 3]}, 2]}

   {Repeated[{___String}, 2]}
*)

Now, we can do something similar to what we did before: define a custom assignment operator:

ClearAll[def];
SetAttributes[def, HoldAll];
def[(h : (Set | SetDelayed))[lhs_, rhs_]] := 
  h @@ Append[
     Hold[lhs] /. t : _Vector | _Atom :> RuleCondition[compile[t]], 
     Unevaluated[rhs]
  ]

where I use a shorter form of Trott - Strzebonski technique, which uses RuleCondition. Now, if we use this one,

def[f[a : Vector[Vector[Atom[String], AnyLength], 2]] := Length[a]]

we get the following definition generated:

?f
    Global`f

    f[a:{Repeated[{___String},2]}]:=Length[a]

One can extend compile to handle more cases. As shown, it is very simplistic, just to illustrate the idea.

At this point, however, we may ask, whether it was worth the trouble to start with the TypeSystem` , if all we want is to define argument tests for a function, using just usual patterns. The conciseness of patterns is probably one reason why people generally don't bother with formal generics / template system just for the purposes of defining functions - in any particular case it is usually much simpler to just write the relevant patterns.

$\endgroup$
2
  • $\begingroup$ Wow, thanks for the answer! I had no idea this library existed. Does any documentation exists for TypeSystem? It doesn't seem to be installed on my system and Google doesn't return much. $\endgroup$
    – Literal
    Commented Apr 22, 2015 at 1:53
  • 1
    $\begingroup$ No, there isn't currently any documentation. This is a new development, it only exists in version 10. It is currently mostly used by Dataset, but can have uses on its own too. $\endgroup$ Commented Apr 22, 2015 at 7:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.