Justin Le writes about Haskell, functional programming, dependent types and applying them to physics and machine learning.
Commenters point to his posts as clear explanations of functors and monads, typed models, fixed-length vectors and Hamiltonian dynamics in Haskell.
You don't need to understand monads to do IO. From [0]:
> There is nothing that has to do with monads at all in printing a string. The idea that `putStrLn "hello world"` is monadic is as absurd as saying that `[1,2,3]` is monadic.
You haven't linked any exemplar tutorials, but I feel confident in saying the ones you're referring to teach the general concept of monads, and not how to do IO with monads.
Quite a number of JavaScript developers learned how to use `andThen` with Promises/A+ back in the day, and they didn't need to learn about monads either. (In fact, when it was raised that promises are really monads, there was serious drama and outrage [1] -- an existence proof if there ever was one, that you can use something productively without understanding it as a monad.)
Likewise, nobody would claim that you need to understand monoids before you can concatenate lists. Concatenating lists is as native to lists as sequencing commands is to IO; it's just part of how those types work. The monad interface abstracts over that idea, and learning that abstraction in its full generality is, typically, what people stumble on.
[0]: https://blog.jle.im/entry/io-monad-considered-harmful.html
[1]: https://github.com/promises-aplus/promises-spec/issues/94
Don't get me wrong; I'm not knocking it. I definitely see the value in these kinds of features.
Like zippers, I'm sure it's another case of "If you understand it, it's really useful."
The point is, you don't necessarily need it to get the job done, and you certainly don't need to be intimidated about it or feel obligated to learn it right off.
Even Haskell isn't actually so difficult to grasp the basics of, if you keep this in mind and just think "Do IO in a 'do' block" and go about your way. See [1].
You don't have to know everything in any language just to get some work done, a lot of times advanced features are just that: advanced, stuff for doing things a bit more efficiently, or to handle certain rough edge cases. Learn them in their own time, and they'll make you better at what you do, but don't get wrapped up too much in expecting perfect efficiency from yourself.
A lot of programmers seem to be, as a people, kinda bad at this kind of self-reflection, like we're all sheep in wolves' clothing trying to avoid showing a hint of weakness. I know I sure am. There's nothing wrong with not knowing something, just try and take a moment learn it when you can.
[1] http://blog.jle.im/entry/io-monad-considered-harmful
> No mon[a|oi]ds necessary.
Mon[a|oi]ds are everywhere, but in some languages you just don't know about it. Which of course lets you not have to learn those concepts knowingly, but they're still there and you still could be a better developer for at least knowing they're there and they're behind some of the repeating patterns you see (string concatenation / promises / etc.)
I believe "not worrying about them at first" is actually a good path to learn these patterns. Using them first, some time later having a thought "oh hey, the API of how JSON decoders are written is kinda similar to how I'm generating random values, that's cool" and only later on learning that the it is in fact monads/monoids/functors/whatever and that they have laws and you can exploit that (eg. [repeated squaring for anything that's a semigroup](https://blog.jle.im/entry/shuffling-things-up.html)) and whatnot.
You can have types in haskell that represent finite length lists. Here's a blog post giving an overview of several methods of doing so (and several examples of libraries in the wild that provide this): https://blog.jle.im/entry/fixed-length-vector-types-in-haskell.html
There's a tradeoff here between generality and how important it is to encode all invariants about a piece of data. Many haskell list processing functions are expressed in terms of right folds, which makes infinite lists fairly unproblematic to work with so long as only part of the list is demanded in the end, however for some cases you may want the type system to ensure you can't receive an infinite list.
He even says explicitly "(never mind how we deal with uniqueness)" in the solution section and has already acknowledged that there are only around 2 million combinations to check.
ETA since he mentions the implementation is a translation of this Haskell implementation: http://blog.jle.im/entry/unique-sample-drawing-searches-with-list-and-statet presumably the uniqueness is done by updating the list of remaining possibilities for selection each time a new value is selected.
See https://blog.jle.im/entry/practical-dependent-types-in-haskell-2.html where you get this function definition: https://github.com/mstksg/inCode/blob/master/code-samples/dependent-haskell/NetworkTyped2.hs#L205-213
I liked this article: https://ren.zone/articles/safe-money where they get e.g. an ExchangeRate "USD" "JPY" type from input that has a different type from ExchangeRate "EUR" "JPY" (and thus can't be used to convert euros, only US dollars).