DEV Community

Murphy Randle
Murphy Randle

Posted on

4 3

Get compiler help, even with “stringly” typed parameters in ReasonML

Sometimes types aren’t enough to help the compiler help you. Sometimes you need to change a function parameter from one string to another string (for example, you want a user’s ID all of a sudden, instead of their name). The semantics are critically different, but the type system has no way to help you pass in the right thing, and you’re left to wanter the codebase on your own, searching out every invocation of the function and hoping you don’t get failed requests because you passed “Sally Mae” to the user service instead of “aw3432refq23f23qfqa23f” (what, your user IDs don’t look like that?)

For example, say the old function looked like this:

let getUser = (userName: string): Js.Promise.t(Js.Json.t) => {
    UserService.getUser(userName)   
};

But now you need it to look like this:

let getUser = (userId: string): Js.Promise.t(Js.Json.t) => {
    UserService.getUser(userId) 
};

Uh oh! The only thing that changed was the name of the “userName” parameter to be “userId”. You save the file, the compiler reports 0 errors, you ship, and the app breaks.

In this case, we can use ReasonML’s labelled arguments to tell the compiler that we need to change all of the invocations of this function.

let getUser = (~userId: string): Js.Promise.t(Js.Json.t) => {
    UserService.getUser(userId) 
};

See that little ~ you threw in there? Adding a tilde (what a strange word) at the beginning of the function name requires that any function callers specify the correct name of that parameter on invocation, and that’s enforced by the compiler. So now at the call site, which looks like this:

getUser(user.name);

You get a compiler error! Every call site gives you a compiler error. You get a wry smile on your face as the terminal leads you through 263 errors, each one fixed simply by changing the code to:

getUser(~userId=user.id);

Except you probably don’t have the user in scope at one of the call sights, and you have to spend the next three months refactoring your application code to get the userId there, instead of the user name. It's okay, it happens.

But, finally, the compiler reports 0 errors. You ship your app. You get no complaints, and you head out for a dip at the beach with your besties. 🏝

Have fun!

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (2)

Collapse
 
hoichi profile image
Sergey Samokhov • Edited

You could also go:

type userId =
  | UserId(string);
let getUser = (userId: userId) /* ... */

Sure, calling getUser(UserId(user.id)) is as verbose as getUser(~userId: user.id)—provided you want to leave those naked strings in all the other places. But maybe it makes more sense to have app-wide userId type anyway and not let strings wander any further than JSON encoding/decoding and suchlike.

You could probably also use phantom types, but I’m not sure if those are easy to use across modules, or whether putting all the user-related business logic in the User module is actually better than just an app-wide type. I’m still new to functional languages.

Collapse
 
mrmurphy profile image
Murphy Randle

Definitely other good approaches. Thanks, Sergey!

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay