7 ms·
While I really like the concept of functional programming and F# is definitely on my list of practical useful languages to learn, this article is clearly writte
by cgrealy 8y ago
While I really like the concept of functional programming and F# is definitely on my list of practical useful languages to learn, this article is clearly written by someone who doesn't know C# very well.
Take the "To hell with interfaces" example.
public interface ISortAlgorithm
{
List<int> Sort(List<int> values);
}
public class QuickSort : ISortAlgorithm
{
public List<int> Sort(List<int> values)
{
// Do QuickSort
return values;
}
}
public class MergeSort : ISortAlgorithm
{
public List<int> Sort(List<int> values)
{
// Do MergeSort
return values;
}
}
public void DoSomething(ISortAlgorithm sortAlgorithm,
List<int> values)
{
var sorted = sortAlgorithm.Sort(values);
}
public void Main()
{
var values = new List<int> { 9, 1, 5, 7 };
DoSomething(new QuickSort(), values);
}
No, no, no, no, nope.
public static class MergeSort
{
public static List<int> Sort(List<int> values)
{
// Do MergeSort
return values;
}
}
public void DoSomething(Func<List<int>, List<int>>
sortAlgorithm, List<int> values)
{
var sorted = sortAlgorithm(values);
}
public void Main()
{
var values = new List<int> { 9, 1, 5, 7 };
DoSomething(QuickSort.Sort, values);
}
No interface necessary.
Or immutability:
"public struct Customer
{
public string Name { get; }
public string Address { get; }
public Customer(string name, string address)
{
Name = name;
Address = address;
}
}
So far so good, but unless someone knows C# very well one could have easily gotten this wrong."
Really?? This is beginner stuff.
I 100% agree that null sucks, functions should not need to be in classes and immutability is great, but this kind of strawman doesn't help his point.
- keithnz 8y agoI agree, it feels like the author is going out of their way to write really really verbose C#
- algorithmsRcool 8y agoYou are right about the first example (although the F# version looks nicer =D) But that struct example is less than great. Just last night I had a readonly C# struct like that, that I used as a dictionary key and wondered why my perf tests took a nose dive when the Dictionary hit a few dozen entries. My boneheaded mistake was i forgot to override GetHashCode and Object.Equals. Great my perf was looking decent, but even then I was allocating 32 bytes of memory on every lookup. I forgot I needed to also implement IEquatable<> to prevent boxing on calls to Object.Equals. My little 2 item struct is 3 lines of definition and 7 lines of boiler plate to get it right. and my GetHashCode impl is suboptimal. F# records do all of that for you. every time. automatically.
- cgrealy 8y agoI 100% agree that F# does this better. but saying "unless someone knows C# very well one could have easily gotten this wrong" is disingenuous at best.
- algorithmsRcool 8y agoDustin is definitely giving a hard sell here I conceed
- drblast 8y agoIn the first example the named interface would be easier for me to read in a large project than Func<List<int>, List<int>> and with actual code there for implementation the two lines to define the interface would end up being a tiny fraction of the total lines of code. An interface would also provide better documentation opportunities for someone trying to write a mock or new implementation. The code in the article isn't outlandish or particularly verbose.
- dustinmoris 8y agoYour sort solution limits which algorithms can be used, because you can't pass in a Func which might need an additional parameter anymore. This is why most C# developers would use an interface for this. With an interface you add extra parameters or dependencies to the constructor of the concrete implementation. If this example wasn't sort algorithms but password hashing algorithms this would be clear. So yeah.. it proves my point... writing good C# is not that easy, requires a lot of experience and mistakes before someone knows how to write SOLID code in C#. Thanks for helping my point!
- algorithmsRcool 8y agoJust to be clear, you are arguing the superiority of partial application/currying here right? Because your F# example is the same except it's trading Func<List<int>, List<int> for (int list -> int list)
- dustinmoris 8y agoI think I just tried to say that I didn't specifically intend to write very verbose C# to make a point. I tried to write good real world production C# and then compare it to how I have seen people do DI in real world F#, which is partial application in this case. There is other ways of doing it too, but this is the most common pattern I've seen.
- AdeptusAquinas 8y agoTechnically its passing 'a list -> 'a list, and only becoming an int array because thats the final input value. The fsharp functions are closer to the C# functions Func<List<IComparable>, List<IComparable>>
- algorithmsRcool 8y agoOr to be even more pedantic it's Func<T[], T[]> where T : IComparable<T> But maybe all of that is just stronger arguments for F#'s inferred typing...
- sparkie 8y agoI primarily use F# over C#, and I would prefer the interface version over the functions in many cases. An interface makes it a bit more explicit and self-explanatory what is expected to be passed. A Func<List<int>, List<int>> is not very meaningful and potentially ambiguous. Obviously, anywhere you expect more than one function, an interface is more desirable because it avoid the need to pass around multiple objects. One thing that makes it a bit nicer in F# than in C# is that you can define object expressions wherever an interface is expected, so there's no need to declare a new type in the static scope somewhere (similar is possible in Java). DoSomething { new ISortAlgorithm with member this.Sort values = ... } values In terms of how they work, there's not really much difference. If something expects a function and you need to capture some local variable within that function, the framework will create a new type for that closure anyway.
- mobiliakas1 8y agoYou can use delegates in C# for that. delegate List<int> SortAlgorithm(List<int> values); List<int> QuickSort(List<int> values) => values; // Do QuickSort List<int> MergeSort(List<int> values) => values; // Do QuickSort void DoSomething(SortAlgorithm sort, List<int> values) { var sorted = sort(values); } public void Main() { var values = new List<int> { 9, 1, 5, 7 }; DoSomething(QuickSort, values); }
- sparkie 8y agoYes, my point was more that they're pretty much the same thing. A delegate and an interface with a single method are essentially isomorphic in their behaviour. The delegate obviously breaks down when you have more than one method. For that interfaces work just fine. Even in the case of using a delegate where you capture variables, the C# compiler creates a new type, instantiates it and passes the method in place of the delegate. It's hardly any different to deriving from an interface. I just tend to use interfaces more out of habit even for single methods. Creating an object expression in F# is pretty much the same outcome as creating a closure in C#. EDIT: I guess the other problem is that F# functions are not directly compatible with Func<_> and Action<_> types, so when calling a C# function expecting these, you need to wrap the call in an anonymous function. C# interfaces and F# classes are compatible though.