Variant primitive type alias

Forgive my ignorance here, but I’ve been struggling with this for over an hour…

I’m trying to create a function that either accepts 2 integers or 2 floats, but I’m having trouble getting the syntax right:

This is incorrect ReScript, but should describe what I’m after:

type number = int | float

let add = (a: number, b: number) =>
  switch a {
  | int => a + b
  | float => a +. b
  }

I suppose I could separate this into 2 functions, but I’m not sure if its the most elegant solution.
Any thoughts on this?

type number = Int(int) | Float(float)

let add = (a: number, b: number) =>
  switch (a, b) {
  | (Int(a), Int(b)) => Int(a + b)
  | (Float(a), Float(b)) => Float(a +. b)
  | (_, _) => raise(Not_found)
  }

add(Int(1), Int(2))->Js.log
add(Float(3.0), Float(4.0))->Js.log
2 Likes

I’m not sure what you’re trying to do ultimately so this solution is likely overkill but it could be a use case for GADTs, example:

type rec number<_> = Int(int): number<int> | Float(float): number<float>

let add:
  type a. (number<a>, number<a>) => a =
  (a, b) =>
    switch (a, b) {
    | (Int(a), Int(b)) => a + b
    | (Float(a), Float(b)) => a +. b
    }

let foo = add(Int(1), Int(2))

let bar = add(Int(1), Float(2.0)) // the compiler will complain here

This would make sure you only use the same kind of number for both arguments.

5 Likes

Thanks for the input, everyone.

Ultimately, I am wanting to build a function that can accept either a float or an int, and return the sum of the 2.

These solutions do help me understand the ReScript language a bit more, but if we have to explicitly encapsulate the numbers with Int() or Float() it might not be worth the extra effort to build a function like this.

I’m working on a very similar hurdle in my app as I’m getting up to speed with rescript. I think you can get close, maybe, with @unwrap and polymorphic variants? But I’m struggling to make this work on my end as well. I’ll offer it up for discussion and hopefully someone smarter can move us both in the right direction :slight_smile:

My attempt looks like this,

  let f = (arr: array<@unwrap [ #Int(int) | #Float(float) ]>): float => {
    ...blah...
  }

with which I’m hoping to enable invocations of the form,

f([1, 2.0, 4, 3.1])

where the elements of the input array are arbitrarily float or int type such that the caller doesn’t need to care about the explicit encapsulation with #Int() or #Float().

What you two are describing looks like a direct transcription of js code, not something you can or even want in rescript. If you want your argument to be of different types, variants are the way to go in rescript. What’s the issue with wrapping your arguments in variants?

3 Likes

In my case the issue is that I’m writing a library in ReScript which I intend to be consumed by TS/JS users, so explicitly wrapping arguments in variants translates to asking my consumers to explicitly wrap their arguments in {TYPE: 'Float', VAL: 1.0} or so.

Actually no, all js numbers can be considered as float in rescript, you don’t have to take ints into consideration.

7 Likes

I think two different functions is actually the best and most elegant solution. In pure ReScript code, you will never have a value in your code that could either be an int or a float. Every value will either be one or the other (or a different type), so it makes sense to just use a specific function for each type.

Trying to use one function on multiple types just isn’t how the language is designed. Wrapping the values in a variant type is one possibility, but it’s not elegant. Using different functions for each type is the norm for statically-typed languages like ReScript.

(This is also why + and +. are two different functions in the first place.)

If you’re able to provide some context for the bigger problem you’re trying to solve, then we may be able to offer some more guidance.

8 Likes

At the end of the day, in ReScript no types are special. We have no ability to say “int and float are related, here is the conversion between them”. Saying your function can accept either a float or an int is the same as saying it can accept either an apple or a TV.

Encapsulating related data in variants or providing multiple conversion functions for similar operations on different types are the only techniques we have available. For example, your int function could convert values to float (which is a no-op when compiled to JS) and then call the float function.

3 Likes