Why I choose Typescript - Specifying generic parameters during call-time.

I’ve had time to sink my teeth into both Flow and Typescript, having built Saul with flow and working with Typescript based projects over the last year.

Warning: Please take what follows with grains of salt. Bias is strong here.

Specifying generic parameters during call-time was the single biggest issue for me when I was dealing with flow, after coming from a Typescript background. Here’s an example of this.

// from: http://www.typescriptlang.org/play/#src=function%20someFactory%3CT%3E()%20%7B%0D%0A%20%20return%20class%20%7B%0D%0A%20%20%20%20method(someParam%20%3A%20T)%20%7B%0D%0A%20%20%20%20%20%20%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0A%2F%2F%20how%20to%20invoke%20this%20factory%20with%20a%20defined%20%3CT%3E%3F%0D%0A%0D%0Aconst%20SomeClass%20%3D%20someFactory%3C%7B%20whatever%3A%20string%20%7D%3E()%0D%0Aconst%20someInstance%20%3D%20new%20SomeClass()%0D%0AsomeInstance.method('will-error-here')%0D%0A

function someFactory<T>() {
  return class {
    method(someParam : T) {
      
    }
  }
}

// how to invoke this factory with a defined <T>?

const SomeClass = someFactory<{ whatever: string }>()
const someInstance = new SomeClass()
someInstance.method('will-error-here')

I very much rely on refining my generic parameters during the call time — as would any developer who relies on making use of generics heavily. In the real world, I found not having this ability to be a dealbreaker when working with higher-order functions and functional composition.

In a nutshell, if I could send a bunch of generic arguments to a function that can give me a response based on those generic arguments, the type system should give me the option of narrowing down the probability space of those generics in every use of the function. Typescript does that, whereas flow does not.

Suppose I have a higher order function definition, like this (this is a real example from one of my projects where this was a higher order react component):

// withAuthentication HOC

export type HOCType<P extends {}, V extends {}> = (
    BaseComponent: React.StatelessComponent<P>,
  ) => React.StatelessComponent<V>;
  
function withAuthentication<OwnProps extends {}>(): 
   HOCType<OwnProps & { user: IUser }, OwnProps> = /* ... /*

Put simply, this will take all the props OwnProps I pass here, and assign a user prop to it.

Let’s consider invoking this with flow:

export const AuthenticatedPage = withAuthentication(Page); // typeof AuthenticatedPage ?

And now with Typescript:

interface IPageProps {
    title: string;
}

export const AuthenticatedPage = withAuthentication<IPageProps>(Page);
  
// typeof AuthenticatedPage is inferred as React.StatelessComponent<IPageProps>
// typeof IPageProps is inferred as React.StatelessComponent<IPageProps & { user: IUser }>

As you can see, Typescript will mark my exported type as something that has the props of IPageProps and the wrapped component as something which will have the user: IUser as a prop.

In flow, I have be explicit and annotate the type of AuthenticatedPage.

This is a bigger deal that HOCs (Higher-order components) though. When using libraries that allow function chaining/composition such as lodash, ramda, immutable, etc, it helps a lot to refine your types along the chain.

Note: This is valid at the time of writing. It’s possible that flow will add support for this in future. But this is something that made me convert an entire React project from flow to typescript, at the time of writing.

Written on July 3, 2017