Moving towards Dylan 2016, the Dylan community would like to address some weaknesses in the language specification and what can be readily expressed in Dylan code. In this post, we'll look at function types as well as provide a brief introduction to some details of the type system implementation within the Open Dylan compiler.

Function Types

One of the big holes in the Dylan type system is the inability to specify function types. What this means is that you can only say that a value is of type <function> and can't indicate anything about the desired signature, types of arguments, return values, etc. This is unfortunate for a number of reasons:

Poor static type safety. The compiler can verify very little involving a function value. It can't warn when the wrong number of arguments or the wrong types of arguments are passed.

The compiler can verify very little involving a function value. It can't warn when the wrong number of arguments or the wrong types of arguments are passed. Less clear interfaces. The type signature of a function must be documented clearly rather than being expressed clearly within the code.

The type signature of a function must be documented clearly rather than being expressed clearly within the code. Optimization is more difficult. Since the compiler can't perform as many checks at compile time, more checks need to be performed at run-time, which limits the amount of optimization that can be performed by the compiler and restricts the generated code to using slower paths for function invocation.

In addition, function types may allow us to improve type inference.

This is something that people have long wanted to have in the Dylan language. According to a comment by Kim Barrett in 2001:

Several people at Apple and Harlequin (and maybe CMU too, I don't remember off hand) spent some time working on this, because it seemed like a serious hole in the Dylan type system. However, it seemed that every attempt at a specification got arbitrarily hairy around variadic parameter lists and return types. I have memories of a whiteboard densely covered with small print purporting to describe all the possible permutations...

There were also discussions about function types as far back as 1992 and 1993 in the early "Dylan partners" communications between Apple, CMU, and Harlequin. As such, it is clear that this will be an effort that will require some time and patience to get right. That said, function types are common in other modern programming languages, especially those from the ML school.

A Motivating Case To guide this discussion, it will be easier to do it in the context of a specific snippet of code. In this case, the code comes from the logging library: for ( item in formatter . parsed-pattern ) if ( instance? ( item , <string> )) write ( stream , item ); else let result = item ( level , target , object , args ); if ( result ) write ( stream , result ); end ; end ; end ; The definition of parsed-pattern is: slot parsed-pattern :: <sequence> ; Given Dylan as it is today, this could be tightened up and specified as: slot parsed-pattern :: limited ( <vector> , of: type-union ( <string> , <function> ));

Poor Static Type Safety In the above example, the compiler can't check to verify that the correct arguments are passed to item . When constructing the parsed-pattern sequence, it also can't verify that the functions in the sequence have the correct signatures. The issue here is that the only thing that the compiler knows about item is that it is of type <function> . It doesn't know about the required function signature, so it can't verify anything. In a normal function call, the compiler knows the signature of the function and is therefore able to perform a number of static checks at compile time.

Less Clear Interfaces While this is less clear in the example above, you can't tell from looking at the slot what sort of function is involved. But an easy example of where the lack of function types makes things more complex is in the map function: define sealed generic map ( fn :: <function> , coll :: <collection> , #rest more-colls :: <collection> ) => ( new-collection :: <collection> ); Ignoring the lack of parametric polymorphism, which will be dealt with in a future blog post, it is clear that it would be nice to have more detail about what sort of function should be passed to map . We would like to have a way to specify that the function passed to map should have a signature congruent with (<object>) => (<object>) .

Optimization Is More Difficult Instead of looking at the full body of code from above, we'll restrict ourselves to the invocation of the item function: let result = item ( level , target , object , args ); When we look at the compiler's IR, we see this: {{ result }} := [CALLo t7({{ level }}, {{ target }}, {{ object }}, {{ args }})] When we look at the generated C, we see: result_ = CALL4 ( T7 , level_ , target_ , object_ , args_ ); Ideally, once more information is present at compile time, we would like to be able to use more efficient calling sequences, perhaps even able to directly invoke the function via its IEP (internal entry point) rather than going through any of the dispatch machinery.