DEV Community

Cover image for Programming Tip #2: Avoid globals for dependencies; instead inject them as function arguments.
Jesse Warden
Jesse Warden

Posted on

Programming Tip #2: Avoid globals for dependencies; instead inject them as function arguments.

Programming Tip #2: In JavaScript, Python, and Lua, avoid globals for dependencies; instead inject them as function arguments.

Image description

Learn More from Justin Searls and Dave Farley below.

Top comments (6)

Collapse
 
laurentpayot profile image
Laurent Payot • Edited

What about this so you can use getName(id) normally outside tests?

const getName = (id, fetchFunc=fetch) =>
    fetchFunc(`https://server.com/api/user/${id}`)
    ...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jesterxl profile image
Jesse Warden

That is a WONDERFUL technique for AWS Lambdas or other places where you cannot modify the function's first parameters, but you want to extend it and have it be testable.

It's also very nice for data-last style programming such as ReScript/OCAML.

... however, once you start doing FP, most of the literature and tutorials I follow all do data last style where you put all your dependencies first, and data last.

Collapse
 
jesterxl profile image
Jesse Warden

Sorry, 1 thing I forgot.

So... when I started learning Functional Programming, I wasn't really familiar or good at currying and partial applications. I knew about pure functions, but not about how FP does dependency injection. I used to use the default parameter style a lot, and data last. However, as learned more, I started learning about function arity, and how most FP languages don't do default parameters because you typically just use partial applications for functions that have defaults built in. This means you always pass arguments to a function, and don't leave things out.

If you do, you use something called unit. It's like an null; it means "nothing", but unlike undefined, you can test for it, and has a distinct value that's different from undefined; it means "intentionally putting nothing here" whereas undefined is usually by accident unless you're mutating something. Other languages like ReScript handle it similiar to how Python handles kwargs; you have parameters that have names, but you don't have to use them. Whether you do or don't, the function needs something to know you're done, and typically that's with unit.

As I got better at FP in JavaScript, I started using curried functions and partial applications more heavily for years. For example, back then, I'd never write:

const getName = (id, fetchFunc=fetch) =>
Enter fullscreen mode Exit fullscreen mode

but instead:

const getName = id => fetchFunc =>
Enter fullscreen mode Exit fullscreen mode

Once I learned about data last, I'd change the above to:

const getName = fetchFunc => id =>
Enter fullscreen mode Exit fullscreen mode

I started learning for unit tests, I needed a fake one so I'd expose it like that:

module.exports = {
    getName
}
Enter fullscreen mode Exit fullscreen mode

... but for integration tests, and for other modules to use the function without having "include your own batters", I'd have those modules expose partial applications with the defaults they needed. This is important because sometimes it's not just a simple thing like fetch, but a multitude of other modules, and sometimes that one module knows what should be private, what is public, vs shared, etc.

module.exports = {
  getName,
  getNamePartial: getName(fetch)
}
Enter fullscreen mode Exit fullscreen mode

Using default parameters like you have it works, and I'd still mix that in specifically with functions I didn't control. I found, even in customizable JavaScript, mucking around with things I don't control, like using Lodash' curryRight to make things like AWS Lambda more comfortable to use wrecked strange havoc on non-FP users of my modules. Calling a function with 2 paramerters when it needs 3 leads to strange exceptions, not normally the same you'd get with undefined is not a function. You start doing operations on functions you didn't mean to and it's quite different runtime error messages compared to Object. Lodash is nice in that it supported the (a)(b)(c) as well as the regular (a, b, c) function calling format, so many only found out by accident, but I realized, your defaultParameter last example is the safest option on things you don't own, or things that are going to be used in public from your module.

However, the rest of your code is hopefully pure functions, with as little side effects as possible, so you end up with a lot of data first style coding. That allows you to JavaScript pipeline operator (or Promises for now if you don't want to brave using unfinished standards) using partial applications. When you start doing that style of coding, you start appreciating more partial applications and data first programming (my brain is currently being rewired as I learn data last in ReScript....). So all of your code ends up with the "stuff" first, and the data last.

I hope that gives more context. It was a long journey for me to learn all that, and my JavaScript changed many times as I learned more, eventually leading me to typed functional languages, but when I return to JavaScript, I try to write is close to FP as possible.

Thread Thread
 
laurentpayot profile image
Laurent Payot

I try to write as close to FP as possible.

Sure, that’s why I’m using Elm for all my front end dev.
Still I’m embracing the standard JS way when writing JS (actually TS) functions.

Nice little set of posts by the way.

Thread Thread
 
jesterxl profile image
Jesse Warden

Thanks! I can't wait till Roc Lang is near beta. Switching my mind from Elm on the front-end to ReScript on the back-end is ROUUUUGH. No types, data last, deprecated |> use -> instead, and discouraged type annotations. I like it, but I like how Roc Lang is "basically Elm on the server" more.

Thread Thread
 
laurentpayot profile image
Laurent Payot

I’m all serverless now, but yes in a perfect word I could write my serverless functions in ROC 🙏