Why doesn't the compiler infer the types for Array.map?

Why can’t the compiler infer the type of photo from this block? If I remove : Fragments.Photo.t I get errors for each property: The record field id can't be found. I feel like the compiler should be able to match the type of photos (array<Fragments.Photo.t>) with the type for the function.

{Array.map(
  (photo: Fragments.Photo.t) =>
    <img src={photo.thumbnail} key={photo.id} />,
  photos,
)->React.array}

Roughly speaking, the compiler searches inside modules for record field names when trying to figure out types. So if a record type is defined in another module, and that module is not ‘open’ in the current scope, it can’t find the field names, and can’t decide what the type is.

The best way to solve this is to tell it what module to look in, e.g.

{photos
  ->Belt.Array.map(
  ({Fragments.Photo.thumbnail: src, id: key}) =>
    <img src key />)
  ->React.array}

Got it, I was afraid of that. I hit the same annoyance with a lot of hooks

let user: NewUser = React.useMemo(_ => {name: "new user"})

The above will warn about not knowing what name is, where I’d expect it to know the type since I explicitly set it.

It should work if you do:

let user = React.useMemo(_ => {NewUser.name: "new user"})
1 Like

Yep, that works too. It just surprises me- it feels like specifying the return type should give the compiler all it needs to infer the type of the data I’m trying to return.

Inference in ReScript doesn’t work exactly like that. The type annotation just tells the compiler, ‘first infer the type, then assert that it matches the annotated type, if it doesn’t, throw a type error’.

So the type annotation doesn’t impact the inference. There is a little bit of nuance here that I’m glossing over, but this is basically it.

1 Like

Aaah, that makes a lot of sense. Thanks!

1 Like

Another way to think about this is the compiler needs to know what type a variable is before it can do anything with it. For example, if there are two map actions that use the same photos variable only the first will need to specify how to find the record fields. Any subsequent use will have that information already.

let keys = photos->Belt.Array.map(p => p.Fragments.Photo.id)

let images = {photos->Belt.Array.map(
    ({thumbnail: src, id: key}) =>
      <img src key />
  )->React.array}

Another way to do this is specify the type in the function argument, as you discovered, but I usually prefer the inline approach.

1 Like