Dealing with a list of options of different types

So this is a very common use-case. I call two different functions one which returns an option of typeA and second which returns an option of type B. I have to make sure that both results contain a value before processing.

let a : option = …
let b : option =

Of course, there could be “N” number of such calls. The way I have done this in other languages is to create a list of of “objects” and then do a “forAll” / every on it.

let listOfOptions : list<option> = [a, b];
switch listOfOptions → Belt.Array.every(x => x.IsSome == true) {
| true => // continue
| false => // something was null log the array and discontinue
}

How do I do this in rescript because there is no common base class between int and string. I don’t want to unnecessarily convert everything to string just to satisfy this requirement. is there a clever approach to resolve this?

is it a fixed number?
we have Option.allN implementations that produce a tuple:

let all4 = (a: option<'a>, b: option<'b>, c: option<'c>, d: option<'d>): option<('a, 'b, 'c, 'd)> => {
  switch (a, b, c, d) {
  | (Some(a), Some(b), Some(c), Some(d)) => Some((a, b, c, d))
  | _ => None
  }
}

[edit: a little tedious, but copilot will write these with a good base example +/-]
[edit: otherwise can dress each value in a variant/polyvariant, or read up on the Untagged Variants thing that is going around right now]

2 Likes

got it.

Solution 1:

type result = {
  name: string,
  age: int,
  salary: string
}
type tupleResult = (option<string>, option<int>, option<float>)

let tuple : tupleResult = (Some("foo"), Some(10), Some(20.0))

let getResult = (t: tupleResult) : option<result> => {
  switch t {
  | (Some(a), Some(b), Some(c)) => Some({name: a, age: b, salary: c})
  | _ => None
  }  
}

Solution2

let (x, y, z) = (Some(10), Some(20.0), Some("foo"))
type result = {
  name: string,
  age: int,
  salary: float
}

open Belt.Option
let resultOption = x -> flatMap(age => 
  y -> flatMap(salary => 
    z -> flatMap(name => 
      Some({name, age, salary}))))

I’ll +1 @mouton’s solution. I’d say it’s quite comparable to what you aimed for with the every approach.

Although, it would be neat with something similar to let-operators, or Rust’s ?-operator which may be more in line with the ReScript-syntax

let foo = foo()?
let bar = bar()?
let baz = baz()?

Some((foo, bar, baz))
1 Like

Depends on the types.
There’s a new direct solution for int and strings. But might not apply in general.

mouton’s Option.allN is nice. For reference, here are two more examples about sequencing options, just to give some more ideas. They are using the same option type, but the sequencing strategies will work in your case too

I wish for these things as well. Though, at least the local exception idea (2nd link) sort of gives you the ? behavior, just less clean.

1 Like