Dealing with collections of a generic type

I’m new to rescript and almost certainly missing a core concept here, so hopefully I can explain this in a way that makes sense. When dealing with a generic type if I want to store it in a collection I have to make a variant wrapper to unify all the generic types, otherwise the collection won’t be homogenous. Take this example code:

//a generic type
type foo<'a> = {value: 'a}
//all of the variants of said generic type that will be used in practice
type anyFoo = IntFoo(foo<int>) | StrFoo(foo<string>)

//create an instnace of the generic type
let makeFoo = val => {value: val}

let bar = makeFoo("asdf")
let baz = makeFoo(123)
let test = IntFoo(baz)
let test2 = StrFoo(bar)

//to make a list of any kind of foo we need to use the variant instead of the generic so it's homogenous
let listOfFoo = list{test, test2}

//...but how do you unwrap it again back into the actual type? This isn't allowed.
//I would think the compiler could easily infer it here but multiple return types are not allowed
//This seems to imply that once you wrap a type in a variant you can only get it back out by pattern matching
//the entire variant at every usage site?
let toFoo = (a: anyFoo) => {
  switch a {
  | IntFoo(v) => v
   //not ok, expected int but got string
  | StrFoo(v) => v
  }
}

Per the comments what I’m failing to understand is how to get the value out of the variant since a function can’t return multiple types. I’m probably using the wrong pattern here, but so far I don’t see how else to make a collection of heterogeneous types. It seems like the only way is to do an exhaustive pattern match at every usage site. What am I missing here?

1 Like

You successfully created the heterogeneous list, but as you can see, generally speaking functions can return only a single type (per each invocation). The main question here is what exactly is toFoo trying to do? How would it make sense for it to sometimes return an int and sometimes a string? That’s dynamically-typed thinking, when you adapt it to static typing this question goes away because the design morphs into something that returns the same type from every branch of the function.

6 Likes

In this case I’m toying with building a reactive state management library. I want to create types that when accessed will register themselves as a dependency to a reactive function context, similar to mobx.

With that in mind, I’d like for the type contained inside my reactive primitives (I’m calling them atoms) to be generic because I don’t really care what it is, I only care about when a caller wants to access the value. That’s what lead me to this design confusion because I also need to store collections of these atoms to keep track of which functions depend upon them and should re-run upon change.

I’m going to keep toying with it to see what I can come up with.

1 Like

There’s a reasonable amount of prior art about this style of reactive computation in adjacent statically-typed language ecosystems. For example, check out Jane Street’s Incremental library which does something very similar to your description: Incremental

2 Likes

Thanks for pointing out that library. The ocaml syntax looks pretty foreign to me but it will likely be a useful reference.

I think I’m really struggling with getting my brain out of a typescript/rust way of thinking and into a more functional way of solving problems. I’d like to design a system like mobx-state-tree where it’s possible to define a structured tree of observable data so that patches/time travel/conflict resolution/etc are possible.

Trying to think of how to model the concept of “actions” (functions that are attached to the tree and allowed to mutate data) has me a bit stumped. So far I can’t think of a way to model an API for “a collection of functions related to some data that might take any arguments or return any arguments”.

I’m likely approaching it from the wrong angle. I’d like to be able to consume this mobx-state-tree-like library from an existing typescript project. Mobx-state-tree is great but it has performance issues and doesn’t work very well with typescript. I think a faster and safer solution might be made with rescript plus it’s a fun exercise in learning a new language.

I think you must resolve the ambiguity at call time. Otherwise, this type system would be regarded as useless as TypeScript.

Given a heterogeneous list of variables, it’s correctly warning you that pattern matching is required to do actual work with the types therein. Is there any issue?

I suggest you make the accessor API depend on the correct interfaces.