Is it "weird" to pass refs into functions?

This is subjective but I’m just curious if anyone has an opinion on it

I’ve been looking into a lot of “imperative shell, functional core” articles, trying to increase my unit test coverage. Working in vanilla and node I need to come up with my own “shell” to handle mutability and side effect stuff.

Online, the examples I see come from F# and scala where they typically just use classes. Since rescript doesn’t have any idea of classes, I’ve gotta choose between using a single ref or defining mutable fields on a type

Most of the time I reach for a ref, but eventually I need to start splitting update actions into different functions, and this is where my question lies. Do you prefer to create a type with mutable, or is passing a ref around to other functions considered okay?

type state = {
  userId: string,
  posts: array<post>
}

let fetchPosts = async (state: ref<state>) => {
  let posts = await fetch(state.contents.userId)
  state := {
    ...state.contents, 
    posts: Array.concat(state.contents.posts, posts)
  }
}
type state = {
  userId: string,
  mutable posts: array<post>
}

let fetchPosts = async (state: state) => {
  let posts = await fetch(state.contents.userId)
  state.posts = Array.concat(state.posts, posts)
}

mutable seems more idiomatic JS but then we’re back to having to worry if a function will change your state or not. ref protects you against that but now the signature requires you to ref() it first (Is that even an issue?)

So what’s your opinion?

1 Like

I think either refs or mutable fields are fine. Refs are just sugar for this after all: type 'a ref = { mutable contents : 'a } (excuse the :camel: syntax).

One the one hand a function that takes a ref, I would definitely assume some side-effect was going to happen. On the other hand, any function that returns unit (or promise<unit>, like these examples), well, that would only be called for side-effects too, so that seems like a wash.

Your second example looks cleaner to me. I’m wondering though…in your state type in the real world, are there actually like tons of mutable fields or just a couple? Eg, if you have a giant record and basically every field is mutable, maybe it would be simpler to stick it in a ref in that case.

By the way, do you have examples of what you’re trying to do with classes, but having trouble/annoyances translating into rescript due to its lack of classes (well, that is if you are having trouble that is). It’s always fun to see class/OO style stuff “translated” to functional style…(as long as you don’t need open recursion of course!)

Perhaps this is besides the point, but my gut reaction would be to hide it behind an interface and pass a function.

Although, if it were up to me, I’d probably do updatePosts(fetchPosts(user)).

Between the two, I think it’s more obvious from the signature that the ref might be mutated.