14 ms·
The combined power of F# and C#
- danjc 3y agoBrevity and clarity are often conflated. Sometimes they're actually inversely correlated.
- ctenb 3y agoYou could make the C# code shorter by using the one-liner record syntax. That would compare the SLOC more fairly.
- piskov 3y agoHere’s version with modern C#: public record Product(string Name, decimal Price); public record OrderLine(Product Product, int Quantity); public abstract record Discount { public record Percentage(decimal Value) : Discount(); public record FixedAmount(decimal Value) : Discount(); } public record Order(List<OrderLine> OrderLines, Discount? Discount) { public decimal CalculateTotalPrice() { var subtotal = OrderLines.Sum(x => x.Product.Price * x.Quantity); return subtotal - Discount switch { Discount.Percentage { } percentage => subtotal * percentage.Value / 100M, Discount.FixedAmount { } amount => amount.Value, _ => 0M }; } }
- CharlieDigital 3y agoI noted the same thing: the C# was unusually verbose. I thought about switching to F# a few months back, but as I started working with it, it wasn't enough of a benefit over C#.
- ctenb 3y agoTo be fair, you'd still need a private constructor in Discount, otherwise the sumtype is not closed, like it is in the F# case. And the percentage Value should be `float` :)
- sparkie 3y agoYou also want the subclasses to be `sealed`. Ideally you would want to write `abstract sealed` for `Discount` too, but C# does not allow it. CIL does support it however, and it is how F# implements sum types. There's no reason I see for C# to continue enforcing this restriction, and lifting it would allow us to write proper DUs.
- ctenb 3y agoWhile I tend to make the SUM type cases sealed, it is theoretically not required to make the sum type closed. All subtypes of a case belong by definition to that case. So any code that handles the defined cases also handles the subclasses implicitly.
- guhidalg 3y agoI developed a very non-trivial ASP.NET Core application mixing F# and C#. I learned you have to take the best parts of both languages to get maximum benefits. Specifically you're going to want a project graph that looks like this: 1. Your top-level project should be a csproj where all your "web" stuff is. Controller routes, DTOs, startup code, etc... This project is responsible for converting data into nice objects that are passed to the F# code. 2. A mid-tier project written in F#, an fsproj. This is where you can be nice, pure, functional, live out your best life pretending C# doesn't exist. However you will eventually run into issues interfacing with external libraries, EF Core, lack of standard library support for whatever you're doing. That's ok! The final layer will solve it. 3. A foundational project written in C#. This project's job is to hide the ugly and make your F# project into the functional programming nirvana you know it can be. Wrap those dependencies into pure functions, write your entity framework junk, use all the latest C# language and runtime features and expose them to your F# project. So something like: C# -> F# -> C# Ultimately, I decided that C# really is good enough and I didn't see enough benefits to continue using F#.
- phillipcarter 3y agoThis is an excellent distillation of how you can effectively mix F# and C# in the same codebase.
- ComputerGuru 3y ago> Ultimately, I decided that C# really is good enough and I didn't see enough benefits to continue using F#. I’ve heard this story repeated so many times over the years. The responsiveness of the C# language team in particular (as opposed to the runtime or ASP teams, etc) is remarkable and the thoughtful inclusion of functional-inspired features over the years has really dramatically improved QOL for C# devs. (With the exception of some annoying carve outs like going too far with trying to please JS/node refugees with not only introducing support for top-level statements but also pushing it as the default in the various templates, the botched delivery of global includes, and other such changes —- but to be fair, many of these were implemented correctly and without any complaints on my end in the language itself but my quibbles are more with the delivery, integration, and polish in the IDE and downstream by teams like the ASP.NET Core one.) After a long pause during the denouement days of the .NET Framework, the C# of today would be unrecognizable to C# devs of yesteryear, and the changes keep coming. (All this is from the POV of a developer that’s been using C# since 2002; back when J# was a thing and I was slowly adapting to writing code in a case-sensitive manner after years of VB.)
- Const-me 3y agoThe C# version is slightly better, though. Binary floating-point numbers are less than ideal for keeping discount percentage because a value like 12.3% can’t be represented exactly.
- tablloyd 3y agoYou can make the C# code much more F# like with records and a switch expression. Eg: https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQAUEEsC2UECeAwgPYB2yMCcAxjCQsgDQAmIA1AD4ACADAATcAjABYA3AFgAUH0FCAdABksZAI6Sp07gGZ+SGgxb9MJFrRgAKYQIByUHBCb8WEGrigAbY9hoQAlBo6eq6G/ADyCC4IymQQFiZmdN6m5k4qMPwAinBQZDBYMAQBWroQZHA4/AAiWMgGcHkAKgQADhD8AN7GEAi+eVAA5o78AGJYAB4QLACCOCQNGQC+JcEGkdW19XkWNXXzTa3tzW1OLm54XgBqnnD+gbr6oRFRFsqUADzPPTEQAHzhkW+KhQTl2WwyYP2MD80g60n4CMEujO7i8RE8NDgHlgEEaJBgnkwWF8/AAvL94YiqV9osDkPIAMoVCwkLzk/is+QJcxcnztABUHI88myuXyhT8/GQAHcCjQABaUqkIuFSZXq/gAN3w/DavTKBKG/Gl8rKGz2C34tU6/GOEBA5vBdq5PT6hvaizJ/z1bsGAsdUPk1w8t34AHp+EJeLwALJMJUahHahD8MATKazKHG01kAOW61dO0OyELZ1jSYzOaWz3stMVzMLeNqxOI5P8MgkEt5L3tzubKEJqmLDSLIA=== https://sharplab.io/#v2:EYLgZgpghgLgrgJwgZwLQAUEEsC2UECeAwgP...
- sbelskie 3y agoFWIW, this a switch expression rather than a switch statement. But in any case I really love this addition to the language but the inability to have multi-line or block expression arms is a constant annoyance for me. You can even combine these with the new one line record syntax to create a poor man’s discriminated union.
- ComputerGuru 3y agoYour pedantic correction is actually important for another reason: the switch expression (unlike the switch statement) is defined at the language level as an expression (evaluating to/“returning” a value), which would be ok except C# doesn’t (at the language level) have a void/unit type, meaning the switch statement has to return an actual value, limiting the places you can use it compared to the F# counterpart (or the match expression from rust, etc) to very specific cases, usually those performing an assignment. The workaround for that is the same as the workaround for the really lame one line limitation: you need to call a (preferably (static) local) function in the handler portion and then return something like `true` assigned to a discard. Hacks all around! Eg _ = foo switch a when … => CaseA(foo), _ => CaseB(foo); With CaseA and CaseB returning bool in order to call a function depending on the value of foo rather than assign a value.
- jcpst 3y agoMicrosoft has a good tutorial that showcases what you can do when designing with switch expressions in mind: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/tutorials/pattern-matching https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals... Probably one of my favorite recent-ish additions to the language.
- shortrounddev2 3y agoI want to like F#, but the syntax is just bonkers to me. There are keywords which are used in only one context, such as `then`, and sometimes an operator means something in one context, but something else in another. e.g: equality/assignment let a: int = 1 // Assignment a = 3 // Testing equality, will give you an error a <- 4 // Assignment, valid There's an article here with more things: https://alexyakunin.medium.com/crying-out-loud-what-i-like-dislike-in-f-c88e746dfcde#:~:text=What%20I%20dislike%20in%20F%23%3A%201%20You%20have,6%20Awful%20type%20constructor%20syntax.%20...%20More%20items https://alexyakunin.medium.com/crying-out-loud-what-i-like-d... I want to like F# since I love C#, but I just want something closer to Scala, but for .Net
- daxfohl 3y agoI felt the same, but once you get used to the syntax, it's much nicer for functional first coding. If you find yourself fighting the syntax a lot, you're probably using it in an imperative style that is not idiomatic in f# anyway.
- galkk 3y agoThe example with <- is not an assignment, it is called shadowing and introduces new value with same name, old one isn’t changed But I agree with some ad hoc words criticism
- gsuuon 3y agoIt is indeed assignment: https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/#symbols-used-in-imperative-expressions https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... shadowing would be a second 'let a = 2'
- galkk 3y agoYeah, my bad but the code from grandparent is also incorrect, as for it to be compilable variable must be declared as mutable. That confused me, as I assumed that code there is compiling. But the only way for it compile is shadowing, so yeah, I kind of assumed that to shadow is to use this syntax. I'm not working with F# for some time, so should've checked before posting.
- bob1029 3y agoI know it's a really trivial matter, but I actually got to use the tuple swap thing the other day and it felt so good. (ItemA, ItemB) = (ItemB, ItemA) ~50% of our methods don't even have block bodies anymore... Switch expressions are our bread & butter now. Once you learn how to combine expressions + switch + LINQ, you have the triforce of poor-man's functional programming inside the perfect imperative realm by which to protect it.
- kkukshtel 3y agoDo you have any other code examples or resources that speak more to this?
- michaelteter 3y agoFunctional core, imperative shell. This seems to be a good approach, especially when you push pretty hard to stuff as much as possible into the functional core. Writing tests is much easier and requires much less code, and bugs are much easier to track down (since you have less surface area of "what might have modified this object I now hold?"). Since a functional approach also goes well with single-responsibility-principle and composability, you can end up with multiple times more actual functions. They're all (ideally) very conceptually simple, but the larger number of more specialized functions means you have to work a lot harder on choosing meaningful names. And to write meaningful names of specific functions, you can end up with some quite long names. This is actually quite ok as it makes things much more readable, but some people have some strong negative reactions to seeing 4-6 word function names. But at least in this case (Java being the most famous counter scenario) each function probably has more real value as it's not just OO layer artefacts.
- kmac_ 3y agoThis approach works very well. Side effects are pushed out of core and no mocks are required for end to end tests. It's quite hard to "sell" to fellow devs, but when it clicks, the code is very clean.
- bob1029 3y ago> Functional core, imperative shell. You can find this pattern in a lot of places if you are a little bit flexible with your definition of "functional". For example, why would we not consider some combination like SQL and PHP to be hinting at this exact same kind of archetype? You've got a crusty outer shell of yucky hackarounds (PHP) that talks to this (ideally) well-normalized & clean data store (SQL). Assuming you "stuff as much as possible" into core part of the solution, you could swap the outer shell for anything you desire without much headache.
- skrebbel 3y agoPHP is more like “imperative core, functional shell”. After all, a PHP request is a pure stateless function call. The only way it can modify state anywhere is with IO (eg store something in the database, write something to the session store, etc) and all data you accumulate in your PHP code gets purged after each request.
- leblancfg 3y agoSomeone at Microsoft better get a trademark on “Powerchord”.
- Dwedit 3y agoYay, the diagram of F#, C#, and VB .net. Because C++/CLI does not exist.
- pshirshov 3y agoWell, I've tried. Interoperability is imperfect and it's painful to use both languages in one project.
- pharmakom 3y agoC# has basically zero value once you learn F#.
- munchler 3y agoI love seeing F# mentioned on Hacker News, but this article in particular is a bit dubious. Yes, you can use F# from C#, but it gets frustrating quickly. Fortunately, almost anything you can do in C# can be done better in F# anyway, so there's really very little need to use C# at all once you take the plunge into functional programming.
- gsuuon 3y agoI agree - I guess this article is more about a (very) gradual way to adopt F# into an existing C# code base, rather than an inherently useful combination of C# and F#.
- munchler 3y agoTrue, but for any serious integration the F# library should expose an API that's designed to be more idiomatic for C# callers. When I see `ListModule.OfSeq` being called from C#, I cringe a little.
- ClimaxGravely 3y agoWould you mind sharing some of the frustrations you have had using F# from C#? I haven't hit anything too hard yet but I also haven't worked on any large projects that are setup like that. In regards to the C# vs F# issues. Have you tried using WPF or any desktop GUI frameworks from F#? If so how was the experience?
- munchler 3y agoHere's a quick example of the kind of trouble you can easily get into when calling idiomatic F# from C#: https://stackoverflow.com/questions/1952114/call-a-higher-order-f-function-from-c-sharp https://stackoverflow.com/questions/1952114/call-a-higher-or... It's been a few years, but yes, I've implemented WPF apps in F# and found it quite doable. FsXaml was very helpful: https://fsprojects.github.io/FsXaml/ https://fsprojects.github.io/FsXaml/
- 3y ago
- jimmaswell 3y agoThe F# is certainly nice, but the C# is odd. This is a textbook example of either inheritance or interfaces. I did inheritance here but it would be trivial to change it to an interface. https://dotnetfiddle.net/F5fPZy https://dotnetfiddle.net/F5fPZy The class implementations add a bit of upfront verbosity but the calculation at the end is arguably nicer to read: that method is concerned with applying the discounts, but in the F# case it has to know how to apply each type of discount, while the C# encapsulates that in the Discount implementation. decimal subtotal = OrderLines.Sum(ol => ol.Product.Price \* ol.Quantity); return Discount?.Apply(subtotal) ?? subtotal; vs let subtotal = List.sumBy (fun ol -> ol.Product.Price \* (decimal ol.Quantity)) orderLines let totalDiscount = discount |> Option.map (fun d -> match d with | Percentage p -> subtotal \* (decimal p / 100M) | FixedAmount f -> f) |> Option.defaultValue 0M subtotal - totalDiscount Though I'm also pretty sure F# could do the same thing with the classes if you wanted. And someone else pointed out you can write some C# that looks the same as the F# if you use a switch statement. And I'm pretty sure you could make it look even more F# if you used more Linq. I guess the F# matching is pretty much like interface/class checking as follows? I don't see the functional difference: if (discount is IPercetage percentage) { .. subtotal \* percentage.value / 100 ... else if (discount is IFixed fixed) { .. subtotal - fixed.value ... And you could do even more with some newer C# features like anonymous classes etc. I'm not left feeling like I'm missing out on anything without discriminated unions in particular. I'd be interested to see some cases where the functional nature of F# does make a large improvement over the equivalent C# though.
- jackmott 3y ago[dead]
- Smaug123 3y agoThe main difference is the totality checking. A discriminated union guarantees that you've handled the entire modelled domain (and nothing else); repeated interface checking merely guarantees that you've handled a subset of it (and also you may have handled a bunch of other stuff). It's so comforting to know up front that you've done everything you need to do!
- 3y ago
- hd4 3y agoWhile there's a thread on F#, I'd like to complain about how I attempted to create/publish a F# dotnet app in entirely using the terminal (i.e. without the bloat of Visual Studio) and it doesn't seem to be supported, which was unfortunate. dotnet new webapp -lang F# -o MyFSharpWebApp -f net8.0 was the command I used. This was in Windows 10. I'll probably attempt it again in WSL2 when I have time, though I'm not expecting better results.
- gsuuon 3y ago> dotnet new list Shows all the available templates - you could give mvc or web a try?
- phillipcarter 3y agoThis is, unfortunately, a consequence of the annoying naming Microsoft does (I am allowed to complain about this because I worked on the .NET team -- nobody can name things we can't). You'll note that in the list of default templates: ASP.NET Core Empty web [C#],F# Web/Empty ASP.NET Core gR... grpc [C#] Web/gRPC ASP.NET Core We... webapi [C#],F# Web/WebAPI ASP.NET Core We... webapp,razor [C#] Web/MVC/Razor Pages ASP.NET Core We... mvc [C#],F# Web/MVC ASP.NET Core wi... angular [C#] Web/MVC/SPA ASP.NET Core wi... react [C#] Web/MVC/SPA "webapp" is actually a flavor of MVC with Razor Pages to define the UI, which is itself a flavor of UI that's different from the typical HTML/JS/react setup most people use. I think the cleanest way to do web development with .NET is to leave .NET-isms out of the UI (.NET UI is, ironically, its weakest link now) and keep it on the server.
- Smaug123 3y agoThe templates are all listed at https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-n... . You picked a template that is indeed not supported in F#; the F# ecosystem is generally oriented more around the SAFE stack and Fable.
- pjmlp 3y agoC++/CLI is missing from the picture, even if it is only available on Windows.
- TillE 3y agoC++/CLI really just exists for creating .NET bindings to C++ libraries, right? If you've got a C interface, LibraryImport in C# is sophisticated enough to get the job done on its own.
- pjmlp 3y agoKind of, originally the plan was to have .NET everywhere. .NET 1.x brought Managed C++, which was replaced by C++/CLI in .NET 2.0. However Office and WinDev were never big fans of it, so its main purpose has been as easier interop for C++ libraries indeed. Also COM and C, as for anyone that is comfortable in C++, is is easier than getting all marshaling annotations correct. However it isn't an option for code that has to be portable, as it relies on Visual C++ toolchain.
- einpoklum 3y ago> Where C# is the most dominant language in the .NET world Why would we want to live in the ".NET world"? I say adopt a programming language, or languages-ecosystem, that's not useful only in the "[some niche] world", but more-or-less everywhere. Whether it's older and well-trodden or newer and up-and-coming, I don't see the benefit of limiting yourself that way.
- WorldMaker 3y agoIt is not really a "niche". The ".NET world" is more-or-less everywhere: it is cross-platform, open source, and runs on Linux/Windows/macOS/iOS/Android/more. The ".NET world" is comparable to the "JVM world" or even the "LLVM world": a virtual machine target with a varied language ecosystem and a lot of real machines supported at runtime (in a cross-platform, open source way today). ".NET world" is defining a complex language ecosystem, and it probably isn't as "limited" an ecosystem as you seem to think.
- einpoklum 3y agoAnd are .NET applications actually getting run on Linux, MacOS, and Android? To a significant extent?
- UK-AL 3y agoThe majority of new .net apps are linux in my experience. Because they all run in containers. Containers are best supported in linux.
- WorldMaker 3y agoEven those not using containers, Linux website hosts are generally cheaper (including on most cloud providers). In addition to new apps I've seen some older web apps pressured into .NET upgrades just to have Linux runtime support to run on cheaper servers.
- sparkie 3y ago.NET is big for mobile app development, particularly when you include Unity. It's really one of the best solutions for writing Android + iOS apps, where you can share a significant amount of code and still have native-looking apps. You don't need to depend on clunky electron which makes your apps huge. .NET apps are smaller and faster than electron based apps. Swift is probably a common choice these days, but wasn't an option a few years ago. Unity is used by a huge number of mobile games and uses C#. The Godot engine also supports C#. On desktops not as much, but it is sometimes used for cross platform development, and plenty of games on Steam are written in Unity and run cross platform. Linux desktop could have had a better story when MonoDevelop was gaining popularity, but Linux/desktop support basically got sidelined when Xamarin shifted focus to mobile development, and the acquisition by MS killed mono. Since MS went fully open source on .NET, the situation for Linux is improving, but it's not yet a choice for most developers on Linux and may likely never be, because everyone wants to write web apps. Many of the Linux desktop apps written with Mono have been abandoned or are barely maintained. There's a few recent apps written with Avalonia which run cross-plaform. Wasabi Wallet (bitcoin privacy focused wallet) is a good example. See more here: https://github.com/AvaloniaCommunity/awesome-avalonia#samples-and-projects https://github.com/AvaloniaCommunity/awesome-avalonia#sample...
- xenadu02 3y agoThere's another power when using the same underlying runtime environment as well: no bridging overhead. If your specific problem set involves a lot of boundary crossing bridging overhead can easily dominate all else. Long ago I integrated IronJS (written in F#) into a project written in C#. IronJS at the time was a very primitive JS compiler though it was a real compiler using the runtime machinery to emit Expression objects that were JIT'd into native code. I was very excited about switching to V8 for the vastly improved performance. To my shock and horror once I got it working the performance was worse. It turned out most scripts called our API a lot (the equivalent of the DOM in a browser). Bridging between CLR types and V8 (C++) types completely erased V8's superior performance. No amount of lazy bridging and zero-copy abstractions were enough and I eventually abandoned that branch. It was a humbling experience that I learned a great deal from.
- oaiey 3y agoThere is a nuance I love to bring up: .NET biggest strength in the apps market today is the best support for native interop. Bridging .NET to Swift/Objective-C or .NET to (Android) Java. Every time there are layer in between, you loose. Either by loosing performance (your case) or by loosing capabilities (like the typical Cordova, React Native or Flutter Plugin). Btw, work of Miguel de Icaza via the Xamarin acquisition.
- kerblang 3y agoSurprised to see that C# doesn't have an Option/Optional type/class yet. Even Java made it over that hump.
- qwerasdf5 3y agoC# has had nullable types for quite a while.
- consilient 3y agoNot quite the same thing. Nullable types are (undiscriminated) unions, `Option` is a sum (= discriminated union).
- oaiey 3y agoIMHO, they are working already a while on discriminated unions. Not yet there.
- i_s 3y agoI'd argue .NET isn't actually that great of a multi language platform. We have a mixed F# and C# solution at work, and that split sometimes causes a lot of friction. If you have some C# code that depends on F# code, that constrains your ability to have F# code depend on C# code. You need to be very careful about how you organize your work to get a good solution (more than on other platforms.) In the JVM world, mixing languages works a lot better, because you can compile .class files instead of whole assemblies. So mixing clojure and Java, for example, is very easy to do in any order.
- raydiatian 3y agoSay it with me everybody “Trade offs”
- ClimaxGravely 3y agoI had a great time writing a static site generator for my website. Initially I wrote it all in C# but I started writing specific components in F#. Currently the only F# portion is the sync-over-FTP feature but I do plan to convert a lot of the data processing portion to F# where if makes sense. I do look forward to having opportunities to mix the two. C# + a gui framework and F# for the non-gui logic works really well in my experience.
- Wolfenstein98k 3y ago"The combined power of F# and C#" So, a power chord?
- deleted 3y ago[deleted]
- datadeft 3y agoIts is great to see F# getting more popular. Please note that seq is not a data structure but an iterator. https://stackoverflow.com/questions/4232130/an-implementation-problem-of-f-seq https://stackoverflow.com/questions/4232130/an-implementatio...
- Avlin67 3y agoF# is nice, I used to work with traders using it... then move to a so called startup in France and CTO laughed when I was speaking about F#... this is why people dont use it more because they never learned FP.... so sad
- substation13 3y agoMany believe that F# is better for functional code and C# is better for imperative code. Hence F# immutable core and C# immutable shell. But I think this is a myth. F# the language is better than C# at most things and certainly better overall. If you are going to use F#, you may as well go all in and get all of the benefits. For web services, Giraffe or Suave combinators are much easier to reason about than ASP.NET MVC patterns.
- spion 3y agoDoes F# still require you to manually order all your source files for the compiler? Sounds like a thing that should've been figured out a while ago (and OCaml doesn't have that limitation anymore with Dune etc)
- sparkie 3y agoIt does, but I see it as a feature, not a bug. Please dontfix.
- spion 3y agoHow is manual oreding of files a feature? Why would automatic ordering be a problem?
- sparkie 3y agoRequired ordering encourages you to organize your code to form an almost directed acyclic graph by minimizing cyclic dependencies between types/functions. The only way you can have the cyclic dependencies is to include them in the same code file and use `type ... and ...`, or `namespace rec`/`module rec`, which are non-defaults and not encouraged. I have a ~300kloc project with zero cyclic dependencies between any types or functions, and its delightful to maintain and test. If I had written this in C# I would've taken the easy way out many times and just made cyclic dependencies between types, which become non unit-testable without creating mock types just to test.
- spion 3y agoWhat if the compiler detected the order itself, found cyclic dependencies and refused to compile if such dependencies exist? It can do that without requiring you to specify your order manually. Ordering by hand or C# are not the only two choices. I could implement this with a super simple prototype where you list dependnat files as comments at the top of each file. I'd honestly rather do that than reoder items in XML by hand.