At the end of this article, we can infer generic types like these.

Continuing where we left off in part 6, let’s add in mutually recursive functions with generics.

Consider the following example:

function even(x) { return x == 0 || odd(x - 1); }

function odd(x) { return x != 0 && even(x - 1); }

We say that these functions are mutually recursive: even calls odd and odd calls even . Because of that, we can’t infer the types of each function in isolation — we must consider both at the same time. Let’s add support for such mutually recursive functions to our syntax tree:

case class EFunctions(

functions : List[GenericFunction],

body : Expression

) extends Expression

The functions is a list of functions that may call each other, and they are in scope in body .

The next thing is that these functions may be generic — that is, they may have a type parameter list <…> , as in the classic map function:

function map<A, B>(array : Array<A>, f : A => B) : Array<B>

A generic function has a name and optionally a generic type. For the parameters and body, we’ll use the lambda function defined earlier in the series:

case class GenericFunction(

name : String,

typeAnnotation : Option[GenericType],

lambda : Expression

)

The GenericType is just a list of type parameters and an ordinary type:

case class GenericType(

generics : List[String],

uninstantiatedType : Type

)

It’s called uninstantiatedType because it may mention some of the type parameters from generics , and we’ll have to remember to instantiate that type, replacing the type parameters with fresh type variables on use.

Finally, when we use a variable, for example map , we need the ability to specify the type arguments, as in map<Int, String>(...) , so we add generics to our variables:

case class EVariable(

name : String,

generics : List[Type] = List()

) extends Expression

Inferring the generic types

Earlier in the series, our environment looked like this: Map[String, Type] . We’re going to change Type to GenericType , so that variables can be generic in our environment. Now our infer function looks like this:

def infer(

environment : Map[String, GenericType],

expectedType : Type,

expression : Expression

) : Expression = expression match {

Whenever we encounter a variable, we’ll look up the variable in the environment and get its generic type back. We then replace each type parameter by a fresh type variable to get an ordinary type. If the user has specified the type arguments explicitly, we constrain each of these fresh type variables to be equal to the corresponding user supplied type.