With C#, when I'm trying to use functional design, I often feel like I'm swimming against the current. The language has functional features, and it will certainly let you use them, but, LINQ aside, the path of least resistance is mostly imperative and object-oriented.
In F#, it's the other way around. It has a full suite of object-oriented features - still, after all these years, quite a bit more complete than C#'s suite of functional features - but the path of least resistance is mostly functional.
I don't want to say that's a universal best way to have things. But it suits my taste, because it makes the easiest way to do things correspond very closely with the way I like to see things done: distinct and well-distinguished layers of functional and object-oriented code, sorted according to where each is of the most utility. C#, by contrast, tends to guide you toward a foamy lather of functional and object-oriented code with no identifiable organizing principle.
I used to think the same, but having now tried FP-style libraries for serializers (Thoth.Json) and HTTP servers (Suave), I think they are far superior to imperative or OOP alternatives. The ability to design these things in a declarative style makes the code much more robust and easier to read.
[0] https://stackoverflow.com/questions/6622524/why-is-haskell-s...
But if you compare F# with C#, is not just LINQ .Select vs List.map
And i am not speaking about function, who is just a part of why i like F#, not the best one
Is the different defaults who matter, who guide you to an easier code who highlight the domain
- structural comparison vs reference comparison - expression vs statement based - immutability
C# will catchup with some features for sure.
Lot of new C# feature remove boilerplate (file scoped namespace, etc) and is a good trend
But the core defaults will be the same.
That will not push to a single direction (if fact you have LOTS of way to write the thing in C#) while F# try to push to the same way
To do so, C# continue to add thing to the LANGUAGE. Now is a lot more complicated than .NET 2.0
While F# try to do the same, but the language doesnt change.
An example: the async/task.
C# added the async as keyword, plus all needed to make it work, and the compiler generate special code.
F# has built that from the general computation expression feature and (recently) the state machine support.
The main downside is that its a lot to learn, and it takes a good while before you get productive. But I am absolutely more productive now in Haskell than just about anything else, save perhaps ad-hoc text munging task; the shell DSL is just so handy for random one off stuff.
Also I've only used node/elixir serverside, but aren't http servers just a huge pipeline? It fits quite well into functional programming.
Absolutely. At a certain level of abstraction they can be viewed in this way.
How do HTTP servers actually get packets to and from the machine? At some point you have to interact with the operating system. This is not a realm where functional programming is very feasible today.
These are two examples I would probably come up with if you asked me “where does FP thoroughly beat imperative?”
(De)Serializers - parser combinator libraries like attoparsec absolutely BTFO imperative parser libraries in most dimensions. Bidirectional codec libraries like haskell’s Binary, Serial, Aeson[1] are top-tier. Functional formatters like Blaze are top-tier as well.
Haskell’s HTTP ecosystem is massively better than anything else I’ve used, and I went on a binge of trying a ton of HTTP servers like 6 years ago (in python, ruby, C++, Go, and finally Haskell).
- make composition ergonomic, idiomatic, and easy (including the usual iterator map/reduce, etc.)
- let the type system protect you (including nullability)
Then you've already got the most important strengths, in my opinion.
<ducks>
Seems like the most significant thing they could do.
I've spent so much time looking into using languages like Rust, Haskell, F# pretty much just because I want a compiled language with DUs. But always lose interest due to more practical requirements like tooling and package ecosystem.
If C# had them, I could stop looking. I would basically use it for pretty much everything except web stuff. And that's coming from someone who used to be very anti-MS. To me C# does everything I want in a language, except for lacking native simple DUs.
There's lots of cool things I've learnt about in FP languages. But discriminated unions are really the only feature that I feel like I'm really missing out on in other languages.
> I strongly believe that functional programming is not a good fit for 100% of software architecture. The best is some sort of hybrid.
And yeah I agree with you. FP is awesome at many things, but not everything. I went down the FP rabbit-hole, it was a and I had many revelations. But once the excitement wears off, I realised that I still like using classes for some things that are just inherently mutable... e.g. GUIs, and progress bars, SQL transactions etc.
Not to mention that being able to type `someObject.` and then get autocomplete for all its methods is a huge time saver, and something that is quite a lot more painful with pure FP where you're much more reliant on your memory and looking things up to find all the functions for something.
I think Rust did really well in balancing the two. I just want Rust's simple struct instantiation and enums in C#, and I could stop wasting time looking for the mythical "perfect language".
Haskell would be a better language in every domain where C# is applicable. That is from a language perspective, abundance of libraries is a different question, as it's a matter of the community size and attention.
doThing1();
// thing1 has finished
doThing2();
Where the functions block. Async and Monads and Futures and all are useful for some things, but mostly I just want to do stuff sequentially, blocking, and not confuse myself.
Scala allows this, although the widespread Future-ification of libraries makes it hard to actually practice.
main :: IO ()
main = do
myBusinessLogic
myBusinessLogic :: IO ()
myBusinessLogic = do
doThing1
doThing2
doThing1 :: IO ()
doThing1 = do
putStr "Hello "
doThing2 :: IO ()
doThing2 = do
putStrLn "World"
---
ghc -o main main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
./main
Hello WorldIf you want to talk more about it, feel free to email me.
There was a time years ago when someone was showing me the types of (>>) and (>>=) in Haskell and my question was "ok, but what does that have to do with IO?!?"
I strongly believe that functional programming is not a good fit for 100% of software architecture. The best is some sort of hybrid. Generally, the closer you get to the business logic, the more functional you would want to be. The serializers, http servers, etc. are probably not worth the squeeze to force into a functional domain.