How to fix "Values do not match" when trying to use type from module functor parameter?

I’m trying to write some generic code using module functors. A simplified version can be found on the Playground.

I try to use a type from a module functor parameter, but it seems I can only see the type declaration from the module type (Locales.t), and not the type I filled in ([#en_GB | #nl_NL]).

[E] Line 32, column 41:
Signature mismatch:
  ...
  Values do not match:
    let translate: ([< #en_GB | #nl_NL], entry) => string
  is not included in
    let translate: (Locales.t, entry) => string
  File "playground.res", line 19, characters 3-46: Expected declaration
  File "playground.res", line 35, characters 7-16: Actual declaration

Could someone explain why that is? Is there a way around this?

With this code:

module MakeLocales = (Params: LocalesParams): Locales => {
  type t = Params.t
  let current = Params.initial
}

MakeLocales returns a module exactly as defined in the module type Locales, which has an abstract t type.

You can explicitly declare that the resulting t type is the same as the Params.t input type:

module MakeLocales = (Params: LocalesParams): (Locales with type t = Params.t) => { ...

You can also remove the type annotation altogether, since it’s not technically required:

module MakeLocales = (Params: LocalesParams) => { ...

It looks like there are still some other issues with the code even after fixing this, but hopefully this helps.

3 Likes

Similar if not the same issue. When I try to use the module I created with an int, I can’t provide an int, only a IntOrd.t which is an abstract type.

See playground

module type OrdType = {
  type t
  let equals: (t, t) => bool
  let compare: (t, t) => int
}

module IntOrd: OrdType = {
  type t = int
  let equals = (a, b) => a == b
  let compare = (a, b) =>
    if a < b {
      -1
    } else if a > b {
      1
    } else {
      0
    }
}

module MakeOrdUtilities = (O: OrdType) => {
  let min = (a, b) =>
    if O.compare(a, b) <= 0 {
      a
    } else {
      b
    }
}

module IntOrdUtilities = MakeOrdUtilities(IntOrd)

let tryIt = IntOrdUtilities.min(1, 2)

====
Without using modules and functors, this works. So now I’m really confused. What are the functors for? Just the data hiding and signature ability?

type ord<'t> = {
  equals: ('t, 't) => bool,
  compare: ('t, 't) => int,
}

type ordUtilities<'t> = {
  min: ('t, 't) => 't,
  max: ('t, 't) => 't,
}

let makeOrdUtilities = ord => {
  min: (a, b) => ord.compare(a, b) <= 0 ? a : b,
  max: (a, b) => ord.compare(a, b) <= 0 ? b : a,
}

let intOrd: ord<int> = {
  equals: (a, b) => a == b,
  compare: (a, b) => Pervasives.compare(a, b),
}

let utils = makeOrdUtilities(intOrd)
let test = utils.min(1, 2)

===
Got the original code to work using this. I don’t think this with syntax is in the documentation. That should be fixed. It “publishes” an abstract type so you can see it I think.

module IntOrd: OrdType with type t = int = {

Whenever there’s an error like this, it’s always what @johnj said above–the module type annotation constrains the type too much and it becomes abstract, so the compiler doesn’t allow using it interchangeably with a concrete type. The solution is always to either remove the module type annotation (module IntOrd = {), or if you absolutely need an annotation (I rarely do), to use with to loosen the abstraction a bit by ‘publishing’ it.

1 Like

I’m running into a similar problem again with Functors and values being “too abstract” and am not sure how to fix it. See this code. The last line works - I can insert a string into the type I made. But if you mouse over the type it shows as StringList.item not string so it isn’t really clear how to use this. Also VS Code shows the members of StringList as having type item = P.item. At this point, I want to erase all references to P. in the code. I’ve tried adding that with type annotation in a few different places but can’t make it compile or figure out where it can go. In the end, I want to make something like UtilitiesType where type t is abstract but for it to be very clear you can insert a string. Also, I don’t really want a type item visible in UtilitiesType but I couldn’t figure out how to compile it all without it.

module type Properties = {
  type item
  let defaultValue: item
}

module type UtilitiesType = {
  type t
  type item
  let insertDefaultItem: t => t
  let empty: t
  let insert: (t, item) => t
}

module type MakeUtilitiesType = (P: Properties) => (UtilitiesType with type item = P.item)

module MakeUtilities: MakeUtilitiesType = (P: Properties) => {
  type t = Belt.List.t<P.item>
  type item = P.item
  let empty = list{}
  let insertDefaultItem = t => t->Belt.List.add(P.defaultValue)
  let insert = (xs, i) => xs->List.add(i)
}

module StringList = MakeUtilities({
  type item = string
  let defaultValue = "bozo"
})

let sample = StringList.empty->StringList.insert("abc")

Try destructive substitution:

UtilitiesType with type item := P.item

That worked. I wish those features were documented on the ReScript web site. Or if there is a way to achieve abstract types with functors without an advanced feature like that then I’d like to know how to do it.

Documentation is an ongoing effort, you should file an issue :slight_smile:

2 Likes

Ok will consider that. I’m not sure what should be documented and what shouldn’t. I just realized I think I can accomplish what I want by explicitly assigning a type to the result of applying a Functor that hides the internals. Like this. In this case I don’t need to know about the with type t... syntax and destructive substitution.

module type SomeValueType = {
  type t
  let someValue: t
}

module MakeSomeValue = (A: SomeValueType) => {
  type t = int
  let someValue = 2
  let anotherValue = 3
}

module TwoNotAbstract = MakeSomeValue({
  type t = int
  let someValue = 2
})

module TwoAbstract: SomeValueType = MakeSomeValue({
  type t = int
  let someValue = 2
})

===

Actually spoke too soon. The above works. But for my real scenario I can’t seem to get it all to work without defining the type for (a) properties going into the functor, (b) result of functor, (c) functor type, which is a function, and this is where I do the destructive substitution. This is a lot of boilerplate. Would be nice to just write out the functor with an inline properties type and get it to somehow work. Don’t know where/how to type the with type stuff directly on my functor. At the end of my functor I tried to attach with type... but that doesn’t seem to work.

===

Can get away without defining an explict type for result of functor - (b). Can do result inline like => ({ ...result} with type...). Not so bad now. I think I was forgetting parenthesis on the result of the functor. I tried => { functor result } with type…` but that did not work. Need the parenthesis.

===

Ok kind of figured out how to do it with the least boilerplate. Really don’t understand the parenthesis and grouping but it works. Here it is very confusing what the type annotation after (P: CollectionPropertiesType) means. It looks like it is saying that CollectionPropertiesType is a function.

  module type CollectionPropertiesType = {
    type item
    let defaultValue: item
  }

  module MakeCollection = (P: CollectionPropertiesType): (
    {
      type t
      type item
      let empty: t
      let insertDefaultItem: t => t
      let insert: (t, item) => t
    }
      with type item := P.item
  ) => {
    type t = Belt.List.t<P.item>
    let empty = list{}
    let insertDefaultItem = t => t->Belt.List.add(P.defaultValue)
    let insert = (xs, i) => xs->List.add(i)
  }

  module StringCollection = MakeCollection({
    type item = string
    let defaultValue = "bozo"
  })

  let z = StringCollection.empty->StringCollection.insert("abc")