--- theme: gaia title: Type-Level Programming in Chapel for Compile-Time Specialization backgroundColor: #fff --- # **Type-Level Programming in Chapel for Compile-Time Specialization** Daniel Fedorin, HPE --- # Compile-Time Programming in Chapel * **Type variables**, as their name suggests, store types instead of values. ```Chapel type myArgs = (int, real); ``` * **Procedures with `type` return intent** can construct new types. ```Chapel proc toNilableIfClassType(type arg) type do if isNonNilableClassType(arg) then return arg?; else return arg; ``` * **`param` variables** store values that are known at compile-time. ```Chapel param numberOfElements = 3; var threeInts: numberOfElements * int; ``` * **Compile-time conditionals** are inlined at compile-time. ```Chapel if false then somethingThatWontCompile(); ``` --- # Restrictions on Compile-Time Programming * Compile-time operations do not have mutable state. - Cannot change values of `param` or `type` variables. * Chapel's compile-time programming does not support loops. - `param` loops are kind of an exception, but are simply unrolled. - Without mutability, this unrolling doesn't give us much. * Without state, our `type` and `param` functions are pure. --- # Did someone say "pure"? I can think of another language that has pure functions... * :white_check_mark: Haskell doesn't have mutable state by default. * :white_check_mark: Haskell doesn't have imperative loops. * :white_check_mark: Haskell functions are pure. --- # Programming in Haskell Without mutability and loops, Haskell programmers use pattern-matching and recursion to express their algorithms. * Data structures are defined by enumerating their possible cases. A list is either empty, or a head element followed by a tail list. ```Haskell data ListOfInts = Nil | Cons Int ListOfInts -- [] = Nil -- [1] = Cons 1 Nil -- [1,2,3] = Cons 1 (Cons 2 (Cons 3 Nil)) ``` * Pattern-matching is used to examine the cases of a data structure and act accordingly. ```Haskell sum :: ListOfInts -> Int sum Nil = 0 sum (Cons i tail) = i + sum tail ``` --- # Evaluating Haskell Haskell simplifies calls to functions by picking the case based on the arguments. ```Haskell sum (Cons 1 (Cons 2 (Cons 3 Nil))) -- case: sum (Cons i tail) = i + sum tail = 1 + sum (Cons 2 (Cons 3 Nil)) -- case: sum (Cons i tail) = i + sum tail = 1 + (2 + sum (Cons 3 Nil)) -- case: sum (Cons i tail) = i + sum tail = 1 + (2 + (3 + sum Nil)) -- case: sum Nil = 0 = 1 + (2 + (3 + 0)) = 6 ``` --- # A Familiar Pattern Picking a case based on the arguments is very similar to Chapel's function overloading. * **A very familiar example**: ```Chapel proc foo(x: int) { writeln("int"); } proc foo(x: real) { writeln("real"); } foo(1); // prints "int" ``` * **A slightly less familiar example**: ```Chapel proc foo(type x: int) { compilerWarning("int"); } proc foo(type x: real) { compilerWarning("real"); } foo(int); // compiler prints "int" ``` --- # A Type-Level List Hypothesis: we can use Chapel's function overloading and types to write functional-ish programs.