Intro To Pattern Matching – Covers C# 9
c-sharpcorner.com
c-sharpcorner.com
Look at how asynchronous code was handled in C# in the past versus today. In the past, you first had to define a delegate type for the signature of the procedure you wanted to run. Then you'd assign something to that delegate, probably a method sitting in a class somewhere. You can now call BeginInvoke on that reference, passing in a callback function. That callback function has to be another method sitting in a class somewhere. You also get this IAsyncResult object returned, with no immediate guidance as to what it's supposed to do. Is it different than the IAsyncResult that gets passed to the callback? No? There's this "AsyncState" object you're supposed to pass, with no clue what it's meant for. If you don't need it, you pass null, which is pretty much every case. Need to return a value? Call EndInvoke with the IAsyncResult object you received in the callback. But wait, where's my delegate instance on which I need to call EndInvoke? Guess I need to hoist it to be a field in my class now. I suppose you could pass it as that AsyncState parameter, but now you're having to do in your head the type-checking job that the compiler should be doing for you.
None of this is guessable from just reading the code. You have to read the documentation in multiple places to figure out what is going on. For added fun, now try composing multiple async operations in sequence.
Contrast to today, we do "Task.Run(<some lambda expression>)". You tell me what is easier to learn?
You see "fewer keywords, fewer features" and see "simplicity". I see those things and I see "more boilerplate for implementing code" > "complexity".
It was just horrible.
One of the greatest powers of pattern matching comes from being abole to match in function definitions. The simplest example:
fac(0) -> 1;
fac(N) when N > 0 -> N*fac(N-1).
This makes a lot of code much more readable and more concise.[1] https://github.com/tc39/proposal-pattern-matching
[2] http://cr.openjdk.java.net/~briangoetz/amber/pattern-match.h...
Much like how the compiler assists you when writing subclasseses so that you've implemented all the required methods, real Pattern Matching (F#) gives you the same guarantees from the compiler that you've handled all the possible cases appropriately.
Exhaustiveness checking is what elevates pattern matching to the other side of the expression problem, which is about getting feedback from the compiler to help you write programs.
a glorified switch statement does not help you do that, and so you still have to write subclasses to get compiler feedback.
They don't in scala either and I had assumed that was to do with type erasure. Given F# does however, presumably there is no limitation to C# doing it?
Because then you would need to limit matching to some kind of closed algebraic data type (ADT) so that compiler knows what is left to be matched on, or provide a catch-all clause.
I thought scala does produce partial warnings matching on case classes
It's 'fixed' in the same sense as the compiler knowing what are the subclasses of a base class.
Just as you can always 'extend' the OOP base class by adding a subclass, you can always 'extend' an algebraic data for by adding another case.
Both can be extended, but more importantly they can be enumerated.
Depends on what kinds of guarantees you want. At the extreme, you can make everything in the system late-bound, and then you simply have a dynamic language instead of a static one.
late-bound != dynamically typed, that's exactly the point - you wish to preserve to the ability to have types loaded at run time without knowing their exact type at compile time while still enjoying strong type guarantees. We can do without run-time loading scenarios at all and still see why this is bad, if one adds a derived class to the code base and then has to address it in a bunch of places rather than have all the new functionality localized to the class then it's a smell
... but the compiler does not know ? new classes are loaded at run-time all the time with e.g. plug-in systems, etc.
Without exhaustiveness it might not be feasible for a statically types PL though, but I don't know nearly enough to even speculate on that.
You can make OOP and FP appear equally useful by framing the discussion with the expression problem, but I think that framing is myopic, and flattens a lot of the actually interesting distinctions and philosophical differences between the two.
I’ve never once used that knowledge professionally to any sort of success. The complexity of polymorphism always ended up biting us (any company/org I’ve worked at) in the ass when things eventually grew way too far apart than what was originally envisioned when the polymorphism made sense.
Kinda hilarious that they are still teaching it with those useless examples though. You’d have thought they could come up with something better by now. I mean, I get why you’d chose something obvious, but in my experience CS students seem to understand polymorphism perfectly well without making a duck quack and a dog go woof.
But what do I know, I can’t even make it work.
Even 1 + 1 is polymorphic, ie., "1" + "1" selects for a different behaviour.
Method overriding may rely on inheritance, and the problem is usually the inheritance not the polymorphism.
I still object on the subtype grounds though -- because the issue is not the polymorphism (that is useful and usually a good idea). It's the inheritance relation, which is not the subtype relation -- eg., you can do subtype polymorphism with just implementing interfaces and there shouldn't be any problems.
This comment is actually expressing issues which have arisen because of inheritance and confusing it with polymorphism.
> The problem comes when you want to add continually new methods all the time
This is not a real problem! Violating encapsulation to avoid dealing with the occasional merge conflict is probably the worst use of pattern matching I've ever seen. The tug-of-war between OO and FP is greatly exaggerated.
One day we wake up and decide we basically know Haskell, OCaml, etc and take off the training wheels?
On the other hand, C# have also gotten deeper support for pass-by-reference and stack allocation which I consider more low level features. So I don't think the development is purely in functional direction.
Will it ever be perfect? Of course not. But I feel like the direction they are taking is a reasonable one.
It's a bit of a shame; I suppose that is one of the reasons that some people in the F# community are really pivoting over to WASM and Fable for using F# in the browser.
Like a poor mans hacky partial application, one argument at a time.
namespace Bored
{
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Console;
internal static class Program
{
private static void Main()
{
var addFive = Add.Partial(5);
addFive(6).Print(); // Prints 11
Enumerable.Range(0, 5).Select(addFive).ForEach(WriteLine); // Prints 5 to 9
}
private static Func<int, int, int> Add = (int x, int y) => x + y;
}
internal static class Extensions
{
public static Func<T2, TR> Partial<T1, T2, TR>(this Func<T1, T2, TR> func, T1 t1) => t2 => func(t1, t2);
public static void Print<T>(this T t) => WriteLine(t);
public static void ForEach<T>(this IEnumerable<T> xs, Action<T> f) => xs.ToList().ForEach(f);
}
}
Define your partial extension method for each arrity to Func. I think it maxes at 16?Functional languages tend to disregard this, and demand composition over mutation. While this works for some domains, it's a burdensome complication for others.
Imperative languages are here to stay.
Do you disagree with this premise? Do you disagree with my claim that the world is dynamic, and that FPs are limited in representing them?
Please address my points specifically if you have anything meaningful to contribute.
Anyway, FYI that's why you've been multiply downvoted. Not because there's some FP cabal who downvote pro-imperative comments.
My comment is a direct response to the OP who implied FPs are going to "take over".
I'm being downvoted probably by FP zealots who can't deal with the idea that their solutions aren't perfect or universal.
BTW, I'm not "against" FP in general at all. I have used it in the past, and it has useful idioms to contribute to imperative languages, and it's perhaps useful on its own in some domains; I'm just pointing out its limitations.
Another example might be a text editor buffer. Immutable persistent data structures allow one to get many features for free such as versioning, undo, lockless autosave. Memory is cheap now, there's less of a need to throw away some my edits with destructive updates. Even filesystems and databases have realised this is good idea. Destructive updates are bad for valuable data.
Imperative programming is only better for writing low-level system code, which is full of state that ultimately no one in the real world cares about.
Imperative programming is the dominant programming model that 99.99% of all real world code for the last 60 years has been programmed in. It very well may be due to be replaced, but certainly cant just be hand-waved away
Imperative programs can have isolated, pure subsections of functionality which are based on composition and immutability, without having to deal with the "ideological" strictness of FP.
You're considering only a minute aspect of large systems. Any real banking system has much more than a ledger to deal with. And most of it involves huge amounts of state, which you can't just pile on top of RAM, or waste CPU on. Anything that doesn't serve a real business/user purpose is just waste.
And text editors don't need FP to support undo either. I'm not sure what point are you trying to make.
The most successful editors in the market, supporting all the features you mentioned, do quite fine with imperative, and in fact, I'm not aware of any meaningful editor written in a FL.
> Memory is cheap now
Not really. This comment strikes me as if it comes from someone who never dealt with significant amount of data.
FP can be extremely wasteful; take head-tail lists for example, which are the default container in many FPs. They waste object-container data, pointer data, and scatter the list items, causing cpu cache misses.
Claiming that imperative is only better for low-level is extremely disconnected from reality. Try writing a game with FP, and I assure you no one will care for code "beauty" when FPS sucks.
Sometimes, arguing with FP purists feels like debating socialists.
Also: go ahead and downvote me, as apparently FP zealots can't tolerate questioning their religion.
And functional programming languages can support imperative programming and mutation, even Haskell. It's a question of what should be the default, which depends on the domain you are working in.
> Any real banking system has much more than a ledger to deal with.
Most banking systems are cobbled together from third-party components and libraries. Low-level languages aren't really needed. Many banks use Python, which is orders of magnitude slower than Haskell.
> This comment strikes me as if it comes from someone who never dealt with significant amount of data.
Actually I have a background in distributed computing, where functional programming abstractions have made processing large amounts of data much easier (MapReduce, Spark).
> FP can be extremely wasteful; take head-tail lists for example, which are the default container in many FPs.
This is a strawman. I can write inefficient code in any language. C++ code can be littered with pointer indirection and memory barriers.
> Sometimes, arguing with FP purists feels like debating socialists.
I am a socialist.
And, if you aren't working within those strictures, you're still just not even in the same universe. No matter how many variables you make immutable, you'll still be able to log events by just executing a log statement, and you'll still be able to create a function that talks to a remote service, and call it from anywhere. So, you might use a few functional-inspired features when they're convenient, but the actual structure of your program is probably still imperative to the core.
I really think delimited-continuation based algebraic effects are the way to go.
Sum types, powerful type inference and and immutable-as-default.
Kotlin, Rust, Typescript are all recent-ish languages an all fit into this category. Meanwhile F# approaches it from the other direction (start with OCaml and sprinkle on some imperative and OO).