Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flow annotations for recursive function

I am interested in how to write flow type annotations for such function:

const Box = x => ({
  map: f => Box(f(x)),
  fold: f => f(x),
});

Type annotations should use Generics, I guess.
Function usage example:

const getRandomColor = (): string =>
  Box(Math.random())
    .map(x => x * 0xFFFFFF)
    .map(Math.floor)
    .map(x => x.toString(16))
    .fold(x => `#${x.padStart(0, 6)}`);

P.S.: If it is impossible, please write a explanation Why it is impossible.
Answer of @Isitea unfortunately doesn't fit because he changed the source code and that was not the point.

like image 387
Slowyn Avatar asked Mar 06 '23 08:03

Slowyn


1 Answers

@Jared's answer only gets us part of the way, but beware of the things it's not enforcing.

How we'd like to write it using flow's generics

const Box = <A>(x:A): Box<A> => {
  return {
    map: <B>(f: A => B): Box<B> => Box(f(x)),
    fold: <B>(f: A => B): B => f(x),
  }
}

Given a simple function and a value of our box, we should be able to verify that the generic type constraint is enforced

const numberToString = (x: number) : string =>
  String (x)

const b : Box<string> =
  new Box("1")

b.map(numberToString) // wups! flow doesn't mind!

As of now, this is broken and doesn't work in flow. @mrkev shows us a workaround using a class

class Box<A> {
  x: A
  constructor (x: A) { this.x = x }
  map<B>(f: A => B): Box<B> { return new Box(f(this.x)) }
  fold<B>(f: A => B): B { return f(this.x) }
}

const numberToString = (x: number) : string =>
  String (x)

const stringToNumber = (x: string): number =>
  Number (x)

const b : Box<string> =
  new Box("1")

// flow accepts this valid program
b.map(stringToNumber)

// flow rejects this invalid program
b.map(numberToString)
//    ^ Cannot call `b.map` with `numberToString` bound to `f` because number [1] is incompatible with string [2] in the first argument.

@thehogfather shares another alternative with a pre-defined type. This is closer to the original form but requires an exhausting amount of typing - 2x what should be necessary if flow offered a more competent type system. Anyway it works, so we're not allowed to complain.

type Box<A> = {
  map: <B>(f: A => B) => Box<B>,
  fold: <B>(f: A => B)=> B
}

const box = <A>(x:A): Box<A> => {
  return {
    map: <B>(f: A => B): Box<B> => box(f(x)),
    fold: <B>(f: A => B): B => f(x),
  }
}

const numberToString = (x: number) : string =>
  String (x)

const stringToNumber = (x: string): number =>
  Number (x)

const b : Box<string> =
  box("1")

// flow accepts this valid program
b.map(stringToNumber)

// flow rejects this invalid program
b.map(numberToString)
//    ^ Cannot call `b.map` with `numberToString` bound to `f` because number [1] is incompatible with string [2] in the first argument.
like image 159
Mulan Avatar answered Mar 20 '23 11:03

Mulan