As far as I can tell F# is one of those things where every single user is extremely happy. This happens rarely and I really am curious about the thing but never had time to get into it. I'm also pretty well versed in the .net ecosystem so it's probably gonna be easy.
Any tips? What kind of workflows might benefit the most if I were to incorporate it (to learn..)?
The funny thing is that you can write very similar code in C#, so maybe you don't need to switch which language you're using as a CLR frontend.
using System.Linq;
using System;
var names = new string[] {"Peter", "Julia", "Xi" };
names.Select(name => $"Hello, {name}").ToList().ForEach(greeting => Console.WriteLine($"{greeting}! Enjoy your C#"));
LINQ is such a good library that I miss it in other languages. The Java stream equivalent just doesn't feel as fluent. As far as fluency goes, that’s not very impressive.
%w{Peter Julia Xi}.map{"Hello, #{it}"}.each{puts "#{it}! Enjoy your Ruby"}
That’s of course trivial examples. And while Ruby now have RBS and Sorbet, it’s yet another tradeoff compared to a syntax that has upfront static analysis as first class citizen in mind.That is, each language will have its strong and weak points, but so far on "fluency" I’m not aware of anything that really beat Ruby far beyond as Ruby does compared to other mainstream programming languages.
Ruby is dynamically typed, which makes "fluent" API design that much easier at the cost of maintainability elsewhere. If you want to compare apples to apples, you need to compare F# to other statically typed languages.
Also note that the following is a valid Crystal-lang code:
%w[Peter Julia Xi].map { |name| "Hello, #{name}" }.each { |greeting| puts "#{greeting}! Enjoy your Crystal" }
As they put it:>Crystal is a general-purpose, object-oriented programming language. With syntax inspired by Ruby, it's a compiled language with static type-checking.
But this time, one can probably say that Crystal will lake the benefits of ecosystem that only a large popular language enjoy.
I guess on that side F#, relying on .Net, is closer to Kotlin with Java ecosystem.
The only difference is that you have to specify the type of the list when you declare it though... That's not really a big deal.
List<string> names = ["Peter", "Julia", "Xi"]; names.Select(name => $"Hello, {name}").ForEach(greeting => Console.WriteLine($"{greeting}! Enjoy your C#"))
or
new List<string> { "Peter", "Julia", "Xi" }.Select(name => $"Hello, {name}").ForEach(greeting => Console.WriteLine($"{greeting}! Enjoy your C#"))
Nothing is that much a big deal on a small selected sample, on the one hand on the other. That is, maybe some will prefer mandatory explicit type for every single variable, and some other will prefer type inference whenever possible, and both have pros and cons.
To jump in a REPL (or any debug breakpoint observation facility), having optional type inference is a great plus to my mind.
Note that Crystal does allow to make type explicit, and keep the fluent interface on track doing so:
Array(String).new.push("Peter", "Julia", "Xi").map{|name| "Hello, #{name}"}.each{|greeting| puts "#{greeting}! Enjoy your Crystal"}
Let’s remark by the way that, like with C# lambda parameters, block parameters are not explicitly typed in that case. You're being way too nice. Java stream is nowhere near as easy to use as LINQ. I'd say that LINQ is easily one of the top 10 coding features that Microsoft has ever created.
I think LINQ is inspired by SQL. You can do whatever you can with SQL, it's just that the data source might differ IEnumerable with some in memory data, IQueryable with some DB. Or you can use async enumerable and your data source can be whatever web API or protocol.
You can write a vast majority of your C# codebase in a functional style if you prefer to.
All the good stuff has been pirated from F# land by now: First-class functions, pattern matching, expression-bodied members, async functional composition, records, immutable collections, optional types, etc.
I wouldn't say "all" - C# doesn't have discriminated unions yet, which is kind of a big one, especially when you're also looking at pattern matching. A
It has been in discussion for quite some time. I believe they'll get there soon: (example) https://dev.to/canro91/it-seems-the-c-team-is-finally-consid...
In the interim, MS demonstrates how C# 8.0+ can fake it pretty well with recursive pattern matching: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Not the same I know, and I would love me a true ADT in C#.
Edit (a formal proposal): https://github.com/dotnet/csharplang/blob/18a527bcc1f0bdaf54...
There's also going to be quite a big ecosystem / standard library difference between languages that had fundamental type system features since the beginning vs. languages that added fundamental features 23 years later.
Imagine all the functions that might return one thing or another, which was inexpressible in C# (until this proposal is shipped), will all these functions release new versions to express what they couldn't express before? Will there be an ecosystem split between static typing and dynamic typing?
Having reviewed the proposal (of course no guarantee that's what discriminated unions (aka product types aka algebraic data types) will look like), and it appears that it very much integrates nicely with the language.
I don't suspect they'll make too many changes (if any) to the existing standard library itself, but rather will put functions into their own sub-namespace moving forward like they did with concurrent containers and the like.
Given their penchant for backwards compatibility, I think old code is safe. Will it create a schism? In some codebases, sure. Folks always want to eat the freshest meat. But if they do it in a non-obtrusive way, it could integrate nicely. It reminds me of tuples, which had a similar expansion of capabilities, but the integration went pretty well.
>that's what discriminated unions (aka product types aka algebraic data types)
Just an FYI, discriminated unions are not product types, they are sum types. It's named so because the total number of possibilities is the sum of the number of possibilities for each variant.
Ex. A sum type Color is a PrimaryColor (Red, Blue, Yellow) OR a SecondaryColor (Purple, Green, Orange), the total number of possibilities is 3 + 3 = 6.
For a product type, if a ColorPair is a PrimaryColor AND a SecondaryColor, the total number of possibilities is 3 * 3 = 9
Both sum types and product types are algebraic types, in the same way that algebra includes both sums and products.
For the standard library, I'm curious for the family of TryParse kind of functions, since those are well modeled by a sum type. Maybe adding an overload without an `out` argument would give you back a DU to `switch` on
I make that mistake more often than I care to, but you are 100% spot-on. They are sum types, not product types. Thank you for making me walk the walk of shame!
While we wait for the official discriminated union feature, the OneOf package is a pretty good stand-in: https://github.com/mcintyre321/OneOf
A language is just as much about what it can't do, then what it can do.
Can you elaborate on what you mean by this?
I assume you are implying that too many choices could confuse a junior developer, which I agree with. However, I don't think this is a concern in the bigger picture when talking about the space of all languages.
My 2p's worth is that the whole of F# is more than the some of its parts. When you say in your previous comment "All the good stuff has been pirated from F#" it misses the point of what it's actually like to use F#. The problem is, it's almost impossible to communicate what it's like. You have to try it and you have to keep going until you get over the initial "WTF!?" hump. There will be a WTF hump.
For example, C# may have cribbed the language features, but F# is expression based and immutable by default. Try using the same features in this context and the whole game changes.
A language by making certain things harder makes the resulting code be built in a certain way.
F# makes mutable code harder to do, so you tend to write immutable code by default.
I don't know if there's a name for it but essentially F# is where the language designers can push the boundaries and try extremely new things that 99% of users will not want or need, but eventually some of them are such good ideas that they feed back into C#.
Maybe that's just research, and I'm glad that Microsoft hasn't killed F# (I do work there, but I don't write F# at work.)
> F# is where the language designers can push the boundaries
It really isn't, not anymore. F# now evolves conservatively, just trying to remove warts and keep up with C# interop.
And even then some C# features were considered too complex/powerful to implement (e.g. variance, scoped refs) or implemented in weaker, incompatible ways when C#'s design is considered messy (e.g. F#'s non-nullable constraints disallow value-types, which breaks for some generic methods written in C#, sadly even part of the System libs).
This isn’t a great example of what linq is good at. There’s no reason to do ToList there, and the ForEach isn’t particularly idiomatic
Yeah, I hit the problem that there isn't a null-type equivalent of Select() for Action<T>, nor is there a IEnumerable.ForEach (controversial), so that's a bit of a hack. But I wanted to make it as close to the original example as possible.
> There’s no reason to do ToList there
In this case, I would move it to the very end if we are concerned about the underlying data shifting when the collection is actually enumerated.
Forgetting to materialize LINQ results can cause a lot of trouble, oftentimes in ways that happily evade detection while a debugger is attached.
> if we are concerned about the underlying data shifting when the collection is actually enumerated
I’m not sure what you mean by this. You can fulfill the IEnumerable contract without allowing multiple enumerations, but that doesn’t really have to do with the data shifting around. Doing ToList can be an expensive and unnecessary allocation
Yes, ForEach isn't idiomatic but he could use Select instead.
Modern C# collection expressions make the definition of names closer to F#:
string[] names = ["Peter", "Julia", "Xi"];
I know working on "natural type" of collections is something the C# team is working on, so it feels possible in the future that you'll be able to do this: var names = ["Peter", "Julia", "Xi"];
Which I think would then allow: ["Peter", "Julia", "Xi"].Select(name => $"Hello, {name}").ToList().ForEach(greeting => Console.WriteLine($"{greeting}! Enjoy your C#"));
I did try that initially and got
<source>(5,1): error CS9176: There is no target type for the collection expression.
.. which I took to mean that, because .Select is an extension method on IEnumerable, the engine was unable to infer whether the collection should be a list, array, or some other type of collection.It seems reasonable to have it default to Array if it's ambiguous, maybe there's a downside I'm not aware of.
Maybe you can submit a proposal/issue to the C# language team? I'd vote for it!
I love LINQ, maybe a little too much. I can end up writing monster oneliners to manipulate data in just the right way. I love list comprehensions in python too, since they can work in similar ways
That could be shortened to
names.ForEach(name=>Console.WriteLine($"Hello, {name}! Enjoy your C#"));
For reference, Rust provides a similar experience
let names = ["Peter", "Julia", "Xi"];
names
.map(|name| format!("Hello, {name}"))
.iter()
.for_each(|greeting| println!("{greeting}! Enjoy your Rust"));
F# shines on the back end, where its functional-first style is very adept at crunching data. Think about data flows in your system: Any place where you use LINQ in C# today to select/filter/transform data might be even better in F#. Parsing is also a great F# use case (e.g. parser combinators), although a fairly narrow niche.
Personally I think F# is excellent for writing ye olde CRUD applications, especially as the business logic becomes more complex. F# is really good at domain modeling, as creating types comes with minimal overhead. C# has improved a lot in this area (eg record types) but it’s still got a long way to go.
I wrote a tutorial about how to get up and running with web dev in F# that might be of interest: https://functionalsoftware.se/posts/building-a-rest-api-in-g...
Thank you, for someone interested in using F#, that is great.
I see you use Giraffe but I wonder how hard would it be to use Web API or to mix F# projects with C# projects in the same solution.
"As far as I can tell F# is one of those things where every single user is extremely happy" Isn't it because language has rather small community of passionate people, who are devoted to their language of choice?
F# popularity is somewhere between CHILL, Clipper and Raku langs, that are probably as obscure as F# for typical software dev.
I know Raku from Perl fame, and F# because it’s Microsoft, but CHILL and Clipper are totally new to me, so in my own humble experience these two latter look far more obscure. :D
I'm pretty sure that there's more production code written in F# than in all those other three combined.