This response defines a function called traceTypes
which provides a quick-and-dirty visualization of type system operation. The function is somewhat fragile as it depends upon undocumented implementation details in version 10.2. Despite this fragility, it might be useful for study purposes as it handles many common type system use cases.
The code for the function can be found at the bottom of this post.
Here is a basic usage example, where we ask the type system to determine what would happen if we started with any value with the same type as {1, 2, 3}
and then applied the operator Sort /* Reverse /* First
:
traceTypes[{1, 2, 3}][Sort /* Reverse /* First]
The screenshot shows the basic features. The Operator column shows the original operator, along with the various suboperators. The Sig column shows whether the operator has any argument type signature rules. If we hover over a Y, a tooltip shows those rules. The A/D column indicates whether the operator is an ascending or descending operator (it presently does not identify mixed operators). Arg Types describes the input argument types for each operator. Result Type shows the final type produced by each operator.
So, reading the last row, we see that the First
operator has two valid signatures, is an ascending operator, and will convert a 3-vector of integers into a scalar integer.
As a second example, consider what happens if we apply Map[#class&]
to the Titanic data:
data = ExampleData[{"Dataset", "Titanic"}] // Normal;
traceTypes[data][Map[#class&]]
If we hover over the E
data type for the class field, then we see that it is an enumeration of three values. The overall trace shows us that we reduce a list of 1309 "structs" to a list of 1309 enumeration values.
A trace can tell us if we make a type error. For example, let's say we try to extract the non-existent class field from the top-level list:
traceTypes[data][#class &]
We can see that the type system will not permit the operation, claiming a problem with "slot 1".
For more complex examples, this failure information combined with the detailed operator breakdown can be quite useful for diagnosing type errors. For example, consider the problem reported in (88810):
traceTypes[{<|"a" -> 1|>}][Merge[Identity] /* (#a &)]
The system accepted Merge[Identity]
, but the resulting type was unknown. Then, when the #a&
was applied to that unknown type, the system rejected it as invalid. But a simple change of operator from #a&
to #["a"]&
makes the operation succeed:
traceTypes[{<|"a" -> 1|>}][Merge[Identity] /* (#["a"] &)]
The resultant data type remains unknown, however.
A trace can reveal the operator-rewriting that occurs when Query
is used:
traceTypes[{<|"a" -> 1|>}][Query[Transpose]]
The repetition of the AssociationTranspose
line is an artifact of duplicate evaluation steps within the trace of type system code evaluation.
Once again, the type system will catch our mistake if we attempt to transpose something that cannot be transposed:
traceTypes[<|"a" -> 1|>][Query[Transpose]]
traceTypes
will operate upon datasets directly, in which case a sequence of operators can be applied, Query
-style:
dataset = ExampleData[{"Dataset", "Titanic"}];
traceTypes[dataset][GroupBy["class"], Min, "age"]
Without further ado, the code follows...
Code
(* Caveat emptor:
This code is designed to work with version 10.2.
It uses undocumented functionality that could change at any time.
*)
Dataset; (* to force auto-loading *)
Needs["TypeSystem`"]
Needs["Dataset`"]
ClearAll[traceTypes]
traceTypes[ds_Dataset][op__] := traceTypes[ds // Normal][Query[op] // Normal]
traceTypes[args___][op_] :=
With[{a0 = TypeSystem`Inference`PackagePrivate`apply0}
, Module[{result = <||>, n = 0, s = {}, in, out, row}
, SetAttributes[{in, out}, HoldAllComplete]
; in[HoldForm@a0[x_, a_]] :=
( ++n; s = {n, s}
; AppendTo[result, n -> <| "op" -> x, "args" -> a, "type" -> Null |>]
)
; out[HoldForm@_a0, r_] :=
(AppendTo[result[First@s], "type" -> HoldForm[r]]; s = Last@s)
; out[x:HoldForm@Catch[_a0, f:FailureType], FailureType[{_, r_}, ___]] :=
AppendTo[result[First@s], "type" -> HoldForm[f[r]]]
; row =
{ #op
, Signatures[#op] /. {s:_[{__}] :> Tooltip["Y", s], _ -> Null}
, If[DescendingQ[#op], "Desc", "Asc"]
, Row[#args, " \[Times] "]
, #type
}&
; ResetTypeApplyCache[]
; TraceScan[in, TypeApply[op, DeduceType /@ {args}], _a0 | _Throw | _Catch, out]
; result //
Query[KeySort /* Values /* Map[row]] //
Prepend[Style[#, Bold]& /@
{"Operator", "Sig", "A/D", "Arg Types", "Result Type"}] //
Grid[#, Frame -> All]&
]
]