Search code examples
javascripttypesecmascript-6flowtypedependent-type

flow types with constant strings, and dependent types


Say I have the following constant string:

export default const FOO = 'FOO'

Say I import this in a flow annotated file like so:

import FOO from '../consts/Foo'

I then have a function:

const example = (foo : string) : {| type: FOO, foo: string |} => {
  return {type: FOO, foo: foo}
}

This doesn't typecheck with:

  6: const example = (foo : string) : {| type: FOO, foo: string |}=> {
                                                         ^^^^^^^^^^^^^^ string. Ineligible value used in/as type annotation (did you forget 'typeof'?)
  6: const example = (foo : string) : {| type: FOO, foo: string |}=> {
                                                         ^^^^^^^^^^^^^^ FOO

So my questions are:

1) is it possible to use constants in flow types, how can I reproduce this behaviour?

2) Is it possible to do dependent types in flow? so for example, could I encode, through types, that the string that is returned must be the same string that is passed into the example function?

EDIT: Clarification to part 2: Is it possible to in some way indicate that the foo parameter passed into the example function is in fact the same string as the string at the foo key in the return object? Or to assert that the input and output have the same length (for say a shift cipher function). Or say contain a permutation of the same characters? (for a shuffle).

https://en.wikipedia.org/wiki/Dependent_type


Solution

  • Instead of declaring FOO as a const, declare it as a disjoint union with just one branch:

    type FOO = "FOO"
    

    Then your code can be updated like this:

    const example = (foo : string) : {| type: FOO, foo: string |} => {
      return {type: "FOO", foo: foo}
    }
    

    If you use any value besides the exact string literal "FOO" where a FOO is required, then it is a compile error.

    If you would prefer to keep your constant, then you'll need to name the type differently, as they would collide. So you could do:

    const FOO = "FOO"
    type FooType = "FOO";
    
    const example = (foo : string) : {| type: FooType, foo: string |} => {
      return {type: FOO, foo: foo}
    }
    

    Unfortunately, I can't see a way to avoid duplicating the string literal, because type disjoint union definition syntax only permits literals and types, not variables even if they are constants.