DEV Community

How to use Dependency Injection in Functional Programming

Jesse Warden on January 16, 2022

Dependency Injection is a technique to make the classes in Object Oriented Programming easier to test and configure. Instead of a class instantiati...
Collapse
 
redbar0n profile image
Magne

How would you compare Elm and ReScript? And which do you prefer?

Collapse
 
jesterxl profile image
Jesse Warden • Edited

which do you prefer?

The only reason I use ReScript is because Elm doesn't officially work on the server, I use AWS at jobs not Lamdera, and Roc isn't ready for prime time yet. That said... I've started to love their "we're imperative and don't care about Category Theory nonsense" style/attitude these OCAML kids have. I've never met a community like that before, it's neat, I'm learning a lot from them. You can learn more about my journey in my video about JavaScript to ReScript.

Ok, comparison.

Elm works on the browser, not the server. ReScript works on both the browser and server. There are ways to hack Elm into a headless state, but it's not fun. It is fun to watch others do it, though.

Elm has no side effects, so all functions are pure. ReScript is like TypeScript; it just compiles to JavaScript, so doesn't have a runtime engine like Elm does. Thus, ReScript's side effects operate just like JavaScript, and there is no I/O Monad type insanity to worry about. Instead, you have side effects everywhere insanity just like you do in JavaScript. This makes ReScript require more unit tests. WAYYY less than JavaScript to be sure, but you need stupider tests around side effects that you don't need in Elm; meaning you have to focus on testing more things.

Elm is good enough with it's types to ensure no runtime exceptions/errors, ever. ReScript is even MORE strict, yet still allows unsafe things through, and has escape hatches making it dangerous if you're not careful. For example, Elm has Maybe, specifically Nothing to handle what we in JavaScript would handle for null or undefined. ReScript is so strict and accurate about compiling to JavaScript, it has 2 completely different modules (well 4, but...) to handle both Undefined and Null in a typed way, including ways to convert back and forth. It really is a symptom of OCAML being a lower-level system language sometimes, and people from that style of language thinking in exacts. That said, it's not thorough, because if you convert an undefined to ReScript's version of Maybe called Optional, you'll get a None, but sometimes you'll get a Some in the case of null vs. undefined. It's better to use the Js.Nullable class to safely convert when you have to deal with JavaScript types coming in like user input or parsing weird JSON. I bet some people love this level of accuracy, and claim it is needed for some reasons, but I prefer Elm's simpler way of dealing with undefined/null: erasing it from existence vs. ReScript giving you thick gloves so you don't burn yourself playing with fire.

Elm's compiler is fast; faster than TypeScript. I've not used it for a gigantor project yet, but I've seen various ways to speed it up so I'm not too worried yet. However, ReScript is light speed. While I tend to do teency microservice/functions in Serverless to ensure my code doesn't get into a large monolith on purpose, I just LOVE how fast I can iterate in ReScript. It's unbelievable how fast the compiler is in a monorepo with like 3 Lambdas and dozens of supporting files. This is the main reason I don't want to use TypeScript compared to ReScript; it's just crazy fast.

I like Elm libraries better. Both languages assume a lot so if you're a beginner it can be pretty overwhelming to even get started. Like, GraphQL library for Elm doesn't tell you how to generate; they just assume you know it's a CLI and will run cli --help and not the default loading from Github. Elm's whole focus is on the beginner and making the complex simple, and many of the library authors are in education or passionate about pedagogy, so this is the exception to the rule most times. My issue with ReScript is were still in this weird time where Reason and ReScript split, but you can still use the Reason libraries in ReScript. It's not really clear, and sometimes when I try, things don't work and there are no errors and I'm like "wtf do I do now?". This is par for the course for JavaScript stuff, though, so I give a ton of leeway to that community; Elm, the opposite, and that works well, because their libraries... always work.

The Elm compiler error messages are better, EVEN IF don't type your functions. Yes, with types they're better, but ReScript ones are nowwhere near as user friendly. Even if you do data-first programming, ReScript still is like "yeah, somewhere your stuff is broke". "Hey, uh... ReScript, how about a... you know... line number to start my investigation, eh, what do you think?" "No, good luck!" Elm's all line numbers, and formatted, and colors, and pretty, and friendly, and hinty... it's just night and day.

I like how Elm has no overuse of (), no need of {}, semi-colons, and isn't as mean as Python about spacing. ReScript has MUCH less need for {}, and doens't need semi-colons either, but you still sometimes have to type it like TypeScript to get better compiler error messages, un-confuse the compiler, and I've grown to like the ML Elm typing style better than the Java esque inline style:

Elm:

add : Int -> Int -> Int
add a b =
  a + b
Enter fullscreen mode Exit fullscreen mode

vs ReScript's inline

let add = (a:number, b:number):number => a + b
Enter fullscreen mode Exit fullscreen mode

ReScript's style is to NOT type because the type inference in OCAML style compilers is just so good, but... maybe I'm doing something wrong, but I've found it's just better to type because the compiler gives messages I can actually read, and sometimes it requires a type to get "unconfused" in long chains.

I like how Elm is data last programming like normal Functional Programming, and ReScript is data-first. I also don't like how ReScript has this data last baggage and makes new packages that are data first to be more friendly to JavaScript developers. I think it's the same stupid tactic the JavaScript devs are trying to do with the Hack style vs. the F# style in the new pipeline operator. If you're a functional programmer, you'll learn to love data last, and all the literature matches. But nNnnNNNnooooOOOo, that's not how the OCAML kids jam. Just hurts my brain to switch back and forth between Elm and ReScript is all, minor nitpick.

I like how Elm has 1 architecture and "that's it". ReScript is like "Dude, we're just a fast compiler with better types than TypeScript and we support functional things". Which means you could use it in Angular or React even if both code bases were heavily Object Oriented. Some find that amazing, I'm like "ugh". That said, it makes it easy for me to use in Serverless, Server, and CLI style architectures, including the Browser when I have to do some things in JavaScript because Elm doesn't support it (i.e. document.cookies). This is just where Elm and ReScript aren't really comparing apples to apples; one is a language, compiler, framework, and runtime whereas the other, ReScript, is just a language and compiler. There's no need for a runtime "because JavaScript" or for a framework "because JavaScript".

... that said, as a team to be full stack ish? They're the shit. I love it.

Collapse
 
redbar0n profile image
Magne • Edited

btw, did you consider Golang or Clojure for the backend?

Golang is more imperative than functional, so that would be a sacrifice. But it is apparently so easy it could be learned in a few days. If you absolutely must have FP, then Clojure could be an alternative, though a bit more foreign to most.

Both Clojure and Golang have best-in-class concurrency (goroutines aka. core/async), and are so fast they use much less resources than Node.

Thread Thread
 
jesterxl profile image
Jesse Warden • Edited

Golang: I did some Golang for 3 months and don't like it. I get why some do, it's a small language with good built-ins/standard library, super fast compiler, super fast language, and the concurrency is easy to grok. Perhaps if I had learned it before my FP days I might be more liking, but I don't need that kind of speed in the stuff I do; mainly back-end API's for front-ends or Lambda functions that aren't long running nor doing a lot of concurrency.

Clojure: It's... weird. So I like the Clojure community; it has some pretty prolific people, and bloggers who've taught me stuff. But, while I respect the JVM's power, I hate configuring/using it, the JVM blows on AWS Lambda in terms of startup time (if I were doing long running Lambdas, my tune would change), and I can't live without my static/strong types.

The above is why I gave up on Elixir/Erlang (even Gleam); I refuse to use EC2's or Docker.

We've used Golang in a 12 Lambda function orchestrated Step Function; 11 were in JavaScript, but we used Go for the one that had to run for 15 minutes, and she was beast (parsing megs of SOAP XML and doing other things at the same time). So I respect it, but again... edge case. I never do perf related work, more UI guy or orchestration API guy more concerned with correctness and data munging. So while I do a lot of HTTP REST concurrency in Node.js, that's the extent of it. If I run out of resources, I just turn up the Lambda memory slider, lelz

Thread Thread
 
redbar0n profile image
Magne • Edited

Thanks, that's very insightful.

I refuse to use EC2's or Docker.

I presume that's because they are not serverless? Just thought I'd mention that with Google Cloud Run you can actually run a Docker container as serverless. It has quite a few benefits over Cloud Functions, one of them being that you can use at least 4 vCPU's instead of just 1 [*], and you can reuse instances concurrently [**]. Thus, if you have a service in Golang you'd be able to utilize all 4 vCPU cores concurrently. Seems like the optimal setup if one wants to maximise resource utilization and minimise cost (haven't done the exact cost calculation, tho). Given that one has a boatload of simultaneous incoming requests, thus the need for speed, that is. Otherwise, one might as well go with Node.js (running ReScript compiled JS) on 1 vCPU.

[*] - But you can also set the Cloud Run vCPU count to just 1: cloud.google.com/run/docs/containe... Which is the better alternative for the single-threaded Node.js, if you don't want to muck around with the Node Cluster module to take advantage of the extra cores (.

[**] I was particularly surprised to find out that with Cloud Functions:

“One of the hidden costs of using serverless Cloud Functions is that the runtime limits the concurrent requests to each instance to one. Arguably, this can simplify some of the programming requirements because developers don’t have to worry about concurrency and usage of global variables is allowed. However, this severely underuses the efficiency of Node.js event-driven IO, which allows a single Node.js instance to serve many requests concurrently. In other words, when using Cloud Functions, the server is functionally busy during the lifetime of a single request. The result of this restricted concurrency in Cloud Functions is that the function may be scaled up if there are more requests than there are instances to handle those requests. For a service under heavy load, this can quickly result in a large amount of scaling. That scaling can have unexpected and possibly detrimental side effects.” source

Thread Thread
 
jesterxl profile image
Jesse Warden

Yeah, that's half of it. The other half is the Docker workflow is just miserable. I always loved in dynamic languages how you could go "node index.js" 50 times a minute, then once you feel it's working, going aws lambda update-function and seconds later invoke your function to test while it's deployed. Docker build even with caching is just slow and miserable and does NOT utilize my skillset. Like, I really don't care about Unix and apk vs apt-get, and why I need these things installed, and what base image to extend, blah blah blah. I just don't find any of that fun. Lambda runs my code fine, I'm not worried about missing some core piece of functionality. Different story in Gitlab pipeline, oh jeez... Alpine is slow installing Ninja for ReScript, but Debian is fast.

That's really weird that Google Cloud does that. AWS doesn't have that Lambda concurrency constraint issue. I'm an AWS kid, so not sure what Google cloud or Azure has over AWS.