I'd imagine it isn't, at least depending on how you define API compatibility, and whether you're only looking at the API interfaces. Imagine two versions of a library that implement the function "add".
Version 1:
add Int -> Int -> Int
add x y = x + y
Version 2:
add Int -> Int -> Int
add x y = x * y
Both versions expose the same API interface, but the functions that conform to that interface are semantically different. A stronger type system could probably differentiate between the two functions, but I doubt you could generally compute whether both functions implement the same behavior.
Perhaps with some sort of functional extensionality you'd be able to compute compatibility perfectly, but I can't imagine that ever being feasible in practice.
That being said, what Elm does offer is still a huge improvement over humans trying to guess whether they made any breaking changes :)
I got my start with computers hacking ROBLOX as a kid! We probably met each other. I went by Shanethe13 / Aeacus back then. If that rings a bell, you should hit me up :)
I actually work in cybersecurity now, directly as a result of ROBLOX. Shedletsky came across some of my work a few months ago, and we reconnected over dinner. It's a crazy small world sometimes.
Most of the suggestions in this post are great, but as always, especially when security is involved, you need to assess your business needs yourself.
The suggestion to use Content-Security-Policy over X-Frame-Options is great -- if you don't expect many of your users to be using IE-based browsers. If you're primarily serving large enterprises or government customers though, it's likely that most of your users will still be coming from a browser that doesn't support Content-Security-Policy.
We use Elixir at Tinfoil Security for our API Security Scanner, and it uses GenStage quite a bit under the hood. It's a big simplification, but we basically have a producer that emits a stream of "scan tasks" to be performed (things like "scan this endpoint for SQL injection"), and then those are consumed by individual worker processes.
It lets us super easily manage things like rate limiting and throttling, while providing backpressure so that an influx of scans (or scans on large APIs) won't overload our infrastructure.
I've found that it really varies based on the language and the tooling it offers. I code Erlang and Elixir on a daily basis, and I've never used such a full-featured debugging environment before. I can trace messages across a distributed system, connect to production nodes to set breakpoints or tracepoints, and I can render a live visualization of all the supervision trees in my application and watch as processes spawn and are killed. And of all that is just scratching the surface of what you can do.
I also remember Racket having pretty powerful debugging capabilities, including the ability to step through programs and see how each expression is evaluated.
Some languages have worse tooling than others, but I don't think it's a problem endemic to functional programming.
The main point of dependency injection isn't that you're passing collaborators into the constructor though -- that's just an implementation detail.
It's about achieving inversion of control, and using a constructor to inject dependencies is just one example of how to do that. There's also setter based DI, and interface based injection, but again, those are just implementation details.
It's not a tutorial, per se, but Game Programming Patterns is a free online book that explores common design patterns in game development: http://gameprogrammingpatterns.com/
As a non-game developer, it's been one of the most invaluable resources I jump too when trying to optimize something.
While I agree with you, it's important to note that the defining trait of Lisp isn't that it's a functional language.
It can be functional, just as it can be object oriented or procedural, but those labels matter less to what Lisp is than does the intense focus on things like metaprogramming, in my opinion.
Lisp teaches you that there's always a better tool for the job than something that's already in your toolkit. More than any other language I've worked with, Lisp makes it incredibly easy and low-friction to write domain-specific languages to solve the exact problem you're working with.
That's not always a good thing -- it can make working with a foreign codebase difficult -- but it's definitely a powerful concept when applied correctly.
It's absolutely okay to be different. In fact, everybody is different, and that's one of the big problems with trying to slot people into one of two buckets.
Only in the case of naive port knocking. You can always generate the knocking sequence with something like TOTP to avoid replay attacks, while also detecting attempts at replaying a previous knock.
Edit: Sorry I misread your comment as talking about replay attacks, not MITM'ing. I'm not an expert, but I believe MITM attacks are typically mitigated by performing the knock out of band over a covert channel (DNS, etc). AFAIK, there isn't really a way to prevent them entirely.
The benefit of port knocking is that it essentially gives you a channel to transmit a password over, without revealing the existence of the system that's being authenticated for. A traditional password, on the other hand, requires some sort of socket that leaks the presence of a listening server.
Even if everybody were to use port knocking, knowing that fact doesn't give you any knowledge about whether a given IP hosts zero, one, or some arbitrary number of possibly vulnerable services.
Security through obscurity is a very misunderstood concept. It should never be used at the expense of proper security (i.e. rolling your own crypto), and you should always act under the assumption that targeted attackers can see through your obscurity, but it can be a valuable part of defense in depth: especially against unskilled attackers.
Consider an 0day for example. When the 0day is published, attackers are going to mass-scan the internet for vulnerable applications. Your WAFs, etc won't yet block the attack, and if you have a vulnerable application that must be externally facing, you may get hit by this mass-scan. If your applications are protected with port knocking, however, you'll have that extra window of time to apply patches and protect yourself before you're directly targeted.
In my experience, it's more important to know that something isn't a monoid / semilattice / <insert structure here>
The CRDTs linked by the parent are a great example. If you're working on an eventually consistent system, and you see a structure that doesn't form a monoid, it's most likely the case that there's going to be some sort of race condition. That isn't to say that any given monoid is going to solve the problem, but it can definitely help to pinpoint possible problems in otherwise complicated code.
It definitely doesn't come up in every discipline, but studying these structures has improved my engineering a ton. If design patterns are about class and object composition, then I'd argue that algebraic structures are the equivalent for function composition.
Full disclosure: I write mostly functional Erlang code for a living
This is a great introduction to monoids. The shape example actually appeared in a study performed in the early 90s [0]. I'm not convinced that you can draw too many valuable conclusions from the study, but it's a fun, quick paper to read.
This has to do with monomorphic versus polymorphic and megamorphic functions. Basically, the inline cache has finite capacity, and if every call of a given function takes objects of the same shape (they share a hidden class), then you don't need to worry about evicting your cache entries.
Once you start passing in objects of different shapes though, you're going to exceed the inline cache's capacity, and start losing out on the massive speedups the cache gains you.
A function that takes one inline cache entry is monomorphic, more than one is polymorphic, and more than the inline cache capacity is megamorphic. You want as many functions as possible to be monomorphic, polymorphic if you can't help it, and never megamorphic.
I'm at Tinfoil Security, and we use Elixir for dynamic security scanning of web applications and APIs. We also have some Phoenix apps, but the bulk of our Elixir use isn't in web development.
It's a very IO-bound problem (how many requests I can make while staying within rate limits / not taking down a service), so Elixir is a great fit for coordinating all of the concurrent work involved in security scanning.
Both versions expose the same API interface, but the functions that conform to that interface are semantically different. A stronger type system could probably differentiate between the two functions, but I doubt you could generally compute whether both functions implement the same behavior.
Perhaps with some sort of functional extensionality you'd be able to compute compatibility perfectly, but I can't imagine that ever being feasible in practice.
That being said, what Elm does offer is still a huge improvement over humans trying to guess whether they made any breaking changes :)