Modelling the business domain, reasoning and managing side effects, avoiding common imperative bugs, these are all valuable skills to develop.
F# is a great language to learn, and very approachable. Worst part about it is interacting with antiquated .NET API's. (I can't believe the state that .NET support for common serialization formats is still in...)
This is not true in my personal experience.
As has been famously said (paraphrased): Functional programming makes tough problems easy and easy problems tough.
In other words the value of functional programming depends on your domain.
They are also a lot less productive. It depends entirely on what you are doing.
That needs a qualifier: it can make easy problems tough if you're not familiar with how to solve them in a functional context.
A big part of that is because smart people have already solved the tough problems and made them available as language features or libraries.
All problems are easy if you are familiar with how to solve them. Unfortunately it's part of the problem to find out how to solve them, and that can be unusually hard in case of functional programming. Like solving something with recursion instead of loops + states. There is a reason cookbooks use loops not recursion.
My favorite example of this is implementing quicksort. It's significantly easier in C than it is in Haskell.
Oh please, what's so hard about
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater
where
lesser = filter (< p) xs
greater = filter (>= p) xs
:)folks, take that with a big ol /s, you would never want to actually use that algorithm. But the real deal isn't all that awful: https://mmhaskell.com/blog/2019/5/13/quicksort-with-haskell
where
(lesser, greater) = partition (< p) xs
I just learned about the existence of this function today, and wonder why I had never seen it used before.https://hackage.haskell.org/package/base-4.20.0.1/docs/Data-...
So, one of the best sorting algorithms ever devised is not actually usable to sort a [a] in Haskell... I maintain this is a good example of making an easy problem tough.
Haskell uses many different collection types, just like any other language. Why not?
> it's actually even slower than the original not-quicksort implementation above, because it actually has to make a copy of the original list, and then return a copy of the mutated array.
Sure, but it could also not do that, if callers are happy to provide a mutable array, just like any other language ...
> one of the best sorting algorithms ever devised is not actually usable to sort a [a] in Haskell
Indeed! One of the best algorithms for sorting a mutable array can't be used on an immutable data type, just like any other language ...
None of this invalidates your original claim that "It's significantly easier in C than it is in Haskell" of course.
I'm not a cheerleader for Haskell, it's not the most appropriate language for every job (certainly not for quicksort). But some folks suddenly become hyper-optimizing assembly programmers whenever anyone has the thought of porting a sql crud app to another language... Horses for courses and all that.
The pragmatic way to look at Haskell is not that it's purely functional, but rather, you could write <del>imperative code</del> Monads if you wanted, and that gives a "functional by default" environment, whereas most imperative languages default to mutable objects etc that are not friendly to functional-style programming.
But then the more modern languages are catching on with immutable by default variables etc. so in the end the differences between newer languages may not be that great after all...
And because of mutual recursion, that means that tough is easy (and easy tough). In other words, if we call the class of tough problems T and easy problems NT, we have T==NT, given FP.
If you try and turn F# into Haskell at home you may run into that problem.
F# is functional first language so if an object oriented or procedural solution is the right call the options right there when you need it.
Even in Haskell, which tries to control side-effects more, it's not hard; it's just that it's stuck with an "IO" annotation. Any function that calls an IO function also becomes an IO function; it's infectious.
main :: IO ()
main = putStrLn "hello, world"Edit: Eh, I thought it was a fun quip.
Hello, world!One way of viewing Haskell is that you are lazily constructing the single composite "effect on the world" called main.
helloWorld :: IO ()
then is a value representing an effect, but it only actually happens when it becomes a part of main.Threads complicate this but don't completely destroy the model.
I familiarized myself with fp to the point of writing scheme and haskell around 15 years ago. Read the classics, understood advanced typing, lambda calculus and so on. The best “fp” I’m using nowadays is closures, currying in the form of func.bind(this[, first]) and map/filter. Which all are absolutely learnable by the means of closures, which are useful but I can live without. Sometimes not having these makes you write effing code instead of fiddling with its forms for hours.
Still waiting for the returns from arcane fp-like code I produced earlier. Cannot recognize nor understand none of my projects in this style that I bothered to save in vcs. Imperative code reads like prose, I have some of it still in production since 2010.
These FP talks are disguised elitism imo (not necessarily bad faith). Beta reduction and monadic transformers sound so cool, but that’s it job-wise.
They may be disguised mathematics. People are into math because it is neat / elegant / cool. So they study it regardless of whether it has a practical use or not.
In practice, it's a pretty well-known phenomenon experienced by many skilled programmers that being forced into different styles by different languages results in learning things that you would only have learned very slowly if you had stuck only to your original language. To be concrete about the "very slowly", I'm talking time frames of your entire career, if not your entire life and beyond. It would be a long time programming in Java before you discover the concept of something like "pure functions" as a distinct sort of function, a desirable sort of function, and one that you might want organize your programming style around.
Of course, having heard of the concept already, we'd all like to fancy ourselves smart enough to figure it out in less than, say, three decades. But we're just lying to ourselves when we do that. Even the smartest of us is not as smart as all of us. You are not independently capable of rediscovering everything all the various programming communities have discovered over decades. If you want to know what even the smartest of us can do on their own without reference to decades of experience of others, you can look into the state of computer programming in more-or-less the 1980s, 90s if you're feeling generous. I think we've learned a lot since then, and the delta between the modern programmer and a 1980s programmer certainly isn't in their IQ or anything similar, it is in their increased collective community experience.
By getting out into systems that force us to learn different paradigms, and into communities that have learned how to use them, we draw on the experience of others and cover far more ground than we could ever have covered on our own, or in the context of a single language where we can settle into a local optima comfort zone. Jumping out of your original community is kind of an annealing process for our programming skills.
"The best “fp” I’m using nowadays is closures, currying in the form of func.bind(this[, first]) and map/filter."
That is really not the lesson about software design that FP teaches, and blindly carrying those principles into imperative programming is at times a quite negative value, as your experience bears out. FP has more to say about purity of functions, the utility of composition of small parts, the flexibility of composition with small parts, ways to wrap parts of the program that can't be handled that way, and providing an existence proof that despite what an imperative programmer might think it is in fact possible to program this way at a system architecture level. I actually agree 100% that anyone whose takeaway from FP is "we should use map everywhere because they're better than for loops and anyone who uses for loops is a Bad Programmer" missed the forest for the leaves, and I choose that modification of the standard metaphor carefully. I consider my programming style highly influenced by my time in functional programming land and you'd need to do a very careful search to find a "map" in my code. That's not what it's about. I'm not surprised when imperative code is messed up by translating that into it.
I wish I skipped this FP tour completely and instead learned how to structure my programs directly. Could save me a year or five. Maybe there’s no better way, but in practice clear explanations are always better than these arcane teachings where you repeat something until you get it by yourself.
OK, then you know about currying, immutable data structures, map/reduce/filter, &c.
Because Java has that since way back when. No real closures, I think, but that doesn't matter much because the anonymous functions do what you want pretty much all the time and you could probably invent your own closures if you really want something else.
It's not a certainty you learn about those things from Java, depends on your team/manager/codebase. None of that is enforced in Java the way it is in fp. Plus, none of it is really core or native to Java, it was added on later.
That's how we got essays back in the 2000s like "The Perils of Java Schools", "Can Your Language Do This", and "Beating the Averages".
https://www.joelonsoftware.com/2005/12/29/the-perils-of-java...
https://www.joelonsoftware.com/2006/08/01/can-your-programmi...
Adding to that, in my case it also made realize that deterministic elimination of entire classes of errors in large, complex code bases, in a systematic rather than ad-hoc way, is actually possible. Prior to discovering fp, and particularly Haskell's type system, I spent much effort trying to do that with a combination of TDD and increasingly elaborate try/catch/throw error handling. Discovering Haskell's compiler, type system, and monadic quarantining of effects obsoleted all that effort and was a huge eye opener for me. And a nice side-effect is easy, reliable refactor-ability. Being able to apply those concepts to imperative and other programming paradigms is where the real value in fp is, imho. Programmers still wrangling with the Tarpit [1] need to take a look if they haven't already.
It might be a matter of taste, but I enjoy code built with functional abstractions that allow neat composable data flows and some caches loitering around. I find it also helps when adding UI. Sometimes performance could be better with mutation, but when I'm at that point I've already spent much more time tuning the thing with caches.
I love F#, the working with C# elements of the language drove me away.
Writing web applications and back-ends in F#, as far as I'm aware, is straightforward and a joy because of integration with the rest of ecosystem.
Also interesting that when FP is mentioned, Hindley-Milner is implicitly understood to be part of FP too even though it doesn’t have to be. Clojure emphasizes immutability and FP but with dynamic typing and everything that comes with that.
The nuances of OOP in F# can be ignored by beginners, so I really wouldn’t let yourself be intimidated coming from Clojure.
I don’t know much about F#, but last time I used it most of its standard library was in C# and .NET, so F# code would interact with objects and classes a lot. AFAIK F# also doesn’t have functor modules, so even without the dependence on C# code, you still can’t avoid classes and objects like you can with OCaml (e.g. you can’t write a generic collection module like `List` or `Set` without functors, it would have to be a collection of a specific type or a class).
It's not a yes/no feature.
https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref...
FTFM: It's not a true/false feature.
Is "emphasizes" just another word for second-class support?
C++ emphasizes the importance of memory safety.
https://www.infoworld.com/article/3714401/c-plus-plus-creato...
Haskell enforces this via a type system.
What safeguards around mutability does Clojure have?
If I import a method 'foo()', is there any kind of contract, notation ... anything which could suggest whether it mutates or not?
Very nearly the entire language and standard library operate on immutable values only. Immutable values are the default, and you will use them for the vast majority of logic. You must do so, unless you very specifically opt to use dedicated reference types, at which point you’ll still need to produce intermediate immutable values to interact with that vast majority of the standard library.
And…
> is there any kind of contract, notation ... anything which could suggest whether it mutates or not?
Functions which mutate state are almost always suffixed !. They will typically fail if you pass them immutable values; they only operate on reference types, which have to be dereferenced (typically with the prefix @) to access their state.
I don't know what's your personal definition of "second-class support" but what it means is that it's explicitly supported by the language.
What safeguards does the language actually put in-place?
I don't think you know what you're talking about. Managing object ownership through systems like smart pointers is not memory safety. Applications that use smart pointers still suffer from memory issues, and it's possible to adopt object ownership systems that still use raw pointers, such as It's object ownership system.
Right. I sound just like someone talking about how "a language which emphasizes immutability" is an OK replacement for a language with pure functions.
Functions in Haskell including Prelude can throw exceptions which is not reflected in the type signature of the function. That is an effect that makes seemingly pure functions impure.
You can’t judge a language from a list of buzzwords. You need to look at how it is used in practice.
No, bottom, or _|_, is an inhabitant of every lifted type. An exception is bottom. So the / function is still pure even though it can throw a divide-by-zero exception.
As stringent as you need it to be (static vs. dynamic types vs. specs), as flexible as you want it to be.
They're often conflated because Haskell is pure and functional and probably the most talked about heavily functional language.
I certainly didn't know that impure functional languages like OCaml existed for ages.
It has exceptions
You can divide by zero
It has unsafe IO primitives
let x = <definition of x>
in ... x ... x ...
(i.e. defining a variable x and then using it some number of times) is equivalent to ... <definition of x> ... <definition of x> ...
Seen in the opposite direction (transforming the bottom code to the top code) this means that extracting repeated code is always a valid thing to do. It's not valid in most other languages, and certainly no mainstream ones.Also, even if Haskell were perfectly pure, the fact that it uses lazy evaluation is far more important to actually being able to make use of referential transparency. In a strict language you will still see a massive performance differences if replacing the first version with the second, in the most common cases.
Ah, well, regardless of whether it holds in Haskell, referential transparency is a well-defined concept. Purity is not a well-defined concept (at least as far as I know. Please share a formal definition if you have one!). That's primarily what I'm trying to say.
But I also disagree with your point about unsafePerformIO. In practice, nothing in Haskell violates referential transparency in a significant way. Who knows why? It's an emergent phenomenon that in principle need not have occurred, but in practice it did. Debug.Trace and similar are about the only things that technically violate referential transparency (and they are extremely tame).
> the fact that it uses lazy evaluation is far more important to actually being able to make use of referential transparency
Yes, I agree with that.
I should note that I agree that, in practice, Haskell is almost always pure and/or referentially transparent. I was just pointing out that technically the GP was correct that it's not perfectly 100% so.
[0] https://www.cambridge.org/core/journals/journal-of-functiona...
1. It assumes that the language is "a conservative extension of the simply typed λ-calculus". That's rather high-powered yet also really restrictive! Haskell doesn't satisfy that requirement. It also assumes the language has functions. Expression languages (i.e. ones without functions) are perfectly reasonable languages and it makes sense to ask whether they are pure.
2. It assumes that a language putatively has multiple evaluation orders (which I suppose is a consequence of the assumption "It is a conservative extension of the simply typed λ-calculus"). Haskell doesn't have multiple evaluation orders. It has one! (notwithstanding you can influence it with seq/!)
If you unpick the essence of what Sabry's really saying you find you can translate it into the Haskell world through imposing two conditions:
C1. soundness of the β-axiom (a.k.a. referential transparency) (this plays the role of Sabry's condition that call by need and call by value have the same result).
C2. That
let x = <definition of x> in ...
gives the same result as let !x = <definition of x> in ...
whenever the latter terminates. (This plays the role of Sabry's condition that call by name and call by value have the same result.) I omitted this condition from my original. I probably shouldn't have because technically it's required, but it risks getting into the weeds of strictness versus laziness.So Sabry's definition of "pure" is a long-winded and restricted way saying something that can be much more conveniently expressed by C1 and C2. If you disagree with me, please demonstrate a property of purity that can't be deduced from C1 and C2!
> I should note that I agree that, in practice, Haskell is almost always pure and/or referentially transparent. I was just pointing out that technically the GP was correct that it's not perfectly 100% so.
OK, fine, but I also said the GP was correct! I am keen to point out, however, that exceptions (including division by zero) do not violate referential transparency (and if someone thinks they violate "purity" that may be a sign that "purity" is ill-defined).
In hindsight I think few would now regret not having added them in the first place.
I strongly believe that there is a point in the PL design space that makes optionals everywhere usable. Maybe Haskell can still be the language that delivers this.
https://hackage.haskell.org/package/bluefin-0.0.3.0/docs/Blu...
To be tongue in cheek then it also has the side effect of heating the CPU.
Exceptions aren't impure anyway.
In some pure functional languages, the pure fragment doesn't have exceptions, and you add exceptions as an effect (or as a monad)
(If you reify the effect with a type like Either or Result, then the code becomes pure: but that's just a monad like said above)
Sure they do, at least in Haskell. Bottom inhabits all types.
Haskell can't express the difference, but languages with effects, like Koka and F*, can!
The first effect is called div in Koka and the second is called exn, section 2.2 here
https://www.microsoft.com/en-us/research/wp-content/uploads/...
Haskell has impure constructs but sets you up to define referentially transparent abstractions with them, and this is actually valued by the Haskell community
This is my experience after spending five years as a Haskell programmer and managing a Haskell team for several years and now moving back to the c++ world to play with AI.
I know lots of good c++ programmers working on cutting edge stuff, real experts in their field, but they sometimes still don't have a clear way to understand how to model data
That is my opinion. It's probably highly contentious.
In my experience FP-aligned people on non-FP projects tend to be more likely to overengineer, more prone to argue in favor of the Great Rewrite For No Reason Except Aesthetics, and more likely to abuse "lesser" programmers when they put up PRs. They suck as team players on teams that are not made of language nerds. I am not just talking about the one person here who I fired, this is a legit pattern I've noticed over at least a half dozen people.
Conversely, they are exactly the right people to deploy when you have really tough, self-contained problems to solve that you wouldn't trust the normal Java 9-5ers to tackle.
No matter how they do it, you can always rewrite their working code in a more maintainable language later once it's working, and make it integrate well with the rest of your stack. :D
Some people don’t understand that the „best solution“ is not necessarily equal to the most beautiful abstraction they can think of.
There, fixed it for you.
A great Haskell programmer (generally speaking) is going to be a culture misfit in any Java, C#, golang, etc shop. I know because I've been that miserable bastard who loves functional programming working with Java devs who don't know anything about FP and couldn't care less. To be clear I'm not saying you can't find a compatible Java shop (I actually did find a startup with a lot of Java devs who appreciated FP and used much of it in Java, and that was pretty great honestly), just that the odds are highly against you.
My biggest advice to people who like FP is: Find a job in a language like Clojure, Elixir, Scala, etc. There are a lot more jobs than you'd think. But if you can't, Ruby and Javascript/Typescript can be pretty close depending on where you go. Talk to existing devs and see how feel about FP in general before you join though!
Yes, it does. It's also dependently typed.
Idris (2), PureScript, Elm, Unison, Roc, Lean (4), Koka, Flix (and some other I've forgotten about).
I still don't understand Haskell, but it did help me learn Rust when I decided to learn that. And I think I could explain what a monad is.
edit: It's a data structure that implements flat map. Hope this saves someone else a few years of their life.
(Of course in practice IO isn't implemented like this, because it would be too slow)
(But in every other language, like Javascript or Python, you can define IO as a data structure. Or even in Haskell itself, you can define for eg. a free monad that gets interpreted later, and it can be made to be just as good as IO itself, though typically people make it less powerful than IO)
However note that every other "computational" monad (like the list monad or the Cont monad) actually is a data structure, even though they describe effects just like IO does. This is because IO is the only possible exception to the "monads are data structures" thing in Haskell (if you don't subscribe to the above view), because Haskell doesn't let you define types that aren't data structures
The only issue with this point of view is that you now need to say what flatMap means for things that are not shaped like arrays. Eg. does it make intuitive sense to flatMap a tree? (A retort is that it must make sense, whatever you call this operation; and flattening a tree means to turn a tree of trees into an one-level tree)
It's not you. Haskell has very bad syntax. It's not hard to understand it, it you rewrite the same things in something saner. Haskell was developed by people who enjoy one-liners and don't really need to write practical programs.
Another aspect of Haskell is that it was written by people who were so misguided as to think that mathematical formulas are somehow superior to typical imperative languages, with meaningful variable names, predictable interpretations of sequences of instructions etc. They, instead, made it all bespoke. Every operation has its own syntax, variables are typically named as they would in math formulas (eg. X and X'). This makes no sense, and is, in fact, very harmful when writing real-world programs, but because, by and large, Haskell never raises to the task of writing real-world programs, it doesn't deter the adepts from using it.
I was under the impression from the History of Haskell [0] that they were interested in unifying research into lazy evaluation of functional programming languages.
> This makes no sense, and is, in fact, very harmful when writing real-world programs,
Gosh, what am I doing with my life? I must have made up all those programs I wrote on my stream, the ones I use to maintain my website, and all the boring line-of-business code I write at work. /s
In all seriousness, Haskell has its warts, but being impractical isn't one of them. To some purists the committee has been overly pragmatic with the design of the language. As far as functional programming languages go it's pretty hairy. You have "pure" functions in the base libraries that can throw runtime exceptions when given the wrong values for their arguments (ie: the infamous head function). Bottom, a special kind of null value, is a member of every type. There exist functions to escape the type system entirely that are used with some frequency to make things work. The committee has gone back more than once to reshape the type-class hierarchy much to the chagrin of the community of maintainers who had to manually patch old code or risk having it not longer compile on new versions of the base libraries. These are all hairy, pragmatic trade-offs the language and ecosystem designers and maintainers have had to make... because people write software using this language to solve problems they have and they have to maintain these systems.
[0] https://www.microsoft.com/en-us/research/wp-content/uploads/...
> Gosh, what am I doing with my life?
I would ask the same question, but unironically. No, you didn't make up those programs of course. I didn't claim that real-world programs are impossible to write in Haskell. I claimed that Haskell is a bad tool for writing real-world programs. People make sub-optimal decisions all the time. That's just human nature... choosing Haskell for any program that would require debugging, long-term maintenance, cross-platform UI, or plenty of other desirable properties is just a very bad choice. But people like you do it anyways!
Why? -- there are plenty of possible answers. If I wanted to look for the flattering answers, I'd say that a lot of experienced and talented programmers like Haskell. So, choosing to write in Haskell for that reason isn't such a bad idea. But, if I wanted to judge Haskell on its engineering rather than social merits: it has very little to bring to the table.
What engineering merits did you have in mind?
I think academia is typically what's meant as an imaginary world, along with play that may be in reference to no world at all.
To be able to maintain something you need to be able to transfer the ownership of the piece of code to someone else. You need to be able to amend the code easily to extend or to remove functionality. It means that the code can be easily split into pieces that can be given to multiple developers to work simultaneously towards a common goal.
Haskell scores poorly on any of those points.
It's very hard to transfer ownership because Haskell programs have too much bespoke syntax that anyone but the original author will not be familiar with. Haskell programs are very hard to understand by executing them because there's no chance of a good step debugger due to the language being "lazy". Haskell programs are always "full of surprises" because they allow a lot of flexibility in the interpretation of the same symbols. The problems C++ programmers complain about when faced with operators overloading are the kind of things Haskell programmers call "Tuesday".
"Pure" functions are a lot harder to modify to add functionality. Typically, such modifications would require swiping changes across the entire codebase. One may argue that this preserves "single responsibility" constraint, but in practice it means a lot more work.
Objects and modules were advertised as a solution to code modularity -- another necessary quality for maintainability. While Haskell has modules, it doesn't really have smaller units of encapsulation other than functions. If two programmers are tasked to work on the same module, they aren't going to have a good time.
> Haskell scores poorly on any of those points
Interesting. I work at a company with over a hundred people committing to a Haskell code-base that runs our entire back-end. As far as I can tell we're continuously shipping code several times a day, every day, with very low impact on our service level indicators.
When we do have production issues they're rarely attributed to Haskell. I can think of one incident in 3 years where a space leak was caught in production. It was straight forward to address and fix before it impacted customers.
In practice, Haskell has been amazing to work with. I just rewrote over a thousand lines of code and shipped that to production without incident. I've never had that go off easily in any other language I've worked with (and I've been around for over twenty years and worked in everything from C, C++, Python, and JS).
And it worked great! And you know why? -- The programmers were skilled at what they were doing. There were about 60 employees in total, and they managed a relatively big codebase for a complicated product (distributed filesystem). Btw, our build system was written in Haskell.
And why do I mention this? -- because I also worked in many other companies, who used all the same languages, typically for much smaller codebases compared to the number of people working on them. And the effects were usually awful.
In conclusion: the choice of language doesn't define the outcome. You may be successful choosing a bad language if you hire good programmers and other stars align to make it happen. You can also easily fail if you use a good language, but other, more important conditions don't hold.
Your individual success is at best an indication that it's possible to use Haskell to achieve your goals. It doesn't mean it's a good tool. You didn't even try to have a test that'd compare Haskell to other languages, you don't have any explanation for why Haskell is good for your case. It just worked "somehow".
What I say, I saw from a more general perspective, and with some justification. But if you need examples rather than justification: at one point in the past, Japanese high-schools used Haskell to teach computer science. I was able to compare the experience to Basic and Pascal which I experienced personally. In the hands of a mediocre-bad programmer Haskell is an awful tool and produces far worse results than Basic or Pascal. And, surprise, most programmers in the world are mediocre-bad. You cannot take your example and extrapolate from it that if everyone used Haskell software would've been better. In fact, there's every indication it wouldn't. In most likelyhood it'd be a lot worse.
My example is only a counter argument that Haskell isn’t suitable because of the reasons you stated. Clearly it can be maintained, work can be split among groups, and delivered with reasonable efficiency… like a lot of software can be in other languages.
And by the way x and x' are totally fine names for a value of a very generic type (or even a very specific type depending on the circumstances), as long as the types and the functions are decently named. I mean, how else would you call the arguments of
splitAt :: Eq a => a -> [a] -> [[a]]
?
There is no need for anything more complex than
splitAt x xs = ...
Those don't seem to be names of parameters, but rather of types. It's missing parameter names entirely.
I spent a good 2 minutes looking at that signature trying to figure it out (and I've read some Haskell tutorials so I'm at least familiar with the syntax). This would've helped:
def split_by<T>(separator: T, list: List[T]) -> List[List[T]]
`sep`, `separator`, `delim`, `delimiter` would've been good names.The rest of the definition is at the end, to see it as a whole:
splitAt :: Eq a => a -> [a] -> [[a]]
splitAt x xs = ...
To clarify, I assumed that by using the constraint `Eq a` and the name splitAt there was no need for extra clarification in the names of the parameters but apparently I was wrong.Haskell has very bad syntax (with extensive backing from Microsoft, iirc the guy who writes the compiler is a Microsoft's Research employee).
F# is a straight-up Microsoft's language.
It doesn't matter what other benefits it has. Just don't touch anything created by that company, and you will have one fewer regrets in your life.
But, if you still want a language from that category: SML or Erlang would be my pick.
:-)
There isn't an Overmind in MS that in a creepy voice tells you to spawn more overlords. Less than that, there doesn't need to be a written document that tells you to give money to MS or your data etc. There's just a general accepted understanding among the people who run that company that ends justify the means. And by "ends" they mean them and their investors getting rich.
If Haskell compiler could've been turned into a money-making machine, and it only required killing off half of Haskell programmer, MS would be working overtime on the plan to hide the bodies, but they'd never even consider the possibility of killing being bad... (metaphorically speaking, hopefully)
* There's no pattern or regularity to how infix / prefix / suffix operators are used which makes splitting program text into self-contained sub-programs virtually impossible if you don't know the exact behavior, including priority of each operator.
* There's a tradition of exceptionally bad names for variables, inherited from the realm of mathematical formulas. In mathematics, it's desirable to give variables names devoid of everyday meaning to emphasize the generic nature of the idea being expressed. This works in the context of very short formulas, but breaks entirely in the context of programs which are usually many orders of magnitude bigger than even the largest formula you've ever seen. There, having meaningful names is a life west.
* It's impossible to make a good debugger for Haskell because of the language being "lazy". Debuggers are essential tools that help programmers in understanding the behavior of their programs. Haskell programmers are forced to rely on their imagination when explaining to themselves how their program works.
* Excessive flexibility. For example, a Haskell programmer may decide to overload string literals (or any literals for that matter). This is orders of magnitude worse than eg. overloading operators in C++, which is criticizes for defying expectations of the reader.
One of these points would've been enough for me to make the experience of working with a language unpleasant. All of them combined is a lot more than unpleasant.
Also, on an aesthetic level, I think a lot of infix operators are kind of ugly. Examples include (<$>), ($), and (<*>). I think Haskell has too many infix operators. This is probably a result of allowing user-definable operators. I do like how you can turn functions into infix operators using backticks, though (e.g. "f x y" can be written as "x `f` y").
having never done F# or haskell, doesn't that start getting into the territory of languages that encourage functional programming like ruby or javascript (modern javascript)?
(F'2023 added a stronger form of "pure" and calls it "simple", but it didn't strengthen the places where a "pure" procedure should be required to be "simple", such as DO CONCURRENT, so being "simple" will be its own reward, if any compiler actually implements it. And a "simple" function's result value can still depend on a mutable pointer target.)
First off, the compiler is single-pass. All your definitions have to be in order. This even extends to type hints - it can't use clues to the right to deduce the type of an expression on the left. This is supposedly for perf reasons, but the compiler can become extremely slow because the inference engine has to work so hard - slower than GHC for sure.
Speaking of slowness, Haskell is surprisingly fast. Idiomatic Haskell can be within 50% the perf of C, since its laziness and purity unlock powerful optimizations. F# is eager and the compiler doesn't do anything fancy. Perf often makes you reach for mutable state and imperative structure, which is disappointing.
The OOP paradigm feels bolted on. Should you use classes or modules? Pure functions or members? It depends, what mood are you in? Unfortunately only member functions support overloads, and overloads are useful for some SFINAE-type patterns with `inline` functions, so they get a bit overused.
`ref` struct support, which is vital for zero-copy and efficient immutable data, have very primitive support. even C# is ahead on this.
Very limited support for implicit conversions, no support for type classes and no function overloading leaves F# with nothing like a numeric tower you'd have in Lisp, and makes building something like Numpy clunky.
I use C# at work, and I love Haskell, so I really wanted to love F#. But it just doesn't get the love it needs from MS, and some design decisions aren't aging well - particularly as C# itself evolves in directions that are tricky for F#'s aging compiler to support.
"It’s quite natural to program in Haskell by building a declarative model of your domain data, writing pure functions over that data, and interacting with the real world at the program’s boundaries. That’s my favorite way to work, Haskell or not."
Haskell can be intimidating, though, so I would recommend F# for most beginners. It supports OOP and doesn't require every single function to be pure, so the learning curve is less intense, but you end up absorbing the same lesson as above.