6 ms·
Really short answer: F# is to C# as C is to Assembler (I regularly use both C# and F# and don't quite agree with this, but it is defensible). Short answer: P
by noblethrasher 12y ago
Really short answer:
F# is to C# as C is to Assembler (I regularly use both C# and F# and don't quite agree with this, but it is defensible).
Short answer:
Programming, especially OOP, has a lot of prescriptions: things like coding against interfaces rather than implementations, using abstractions, the law of Demeter, avoiding mutations, prefer composition to inheritance, etc.
Basically, you can think of F# as a language that compiles down to C#, but follows all of the best practices without fuss.
Consider this example of F# code:
type Shape =
| Circle of float
| Square of float
let area s =
match s with
| Circle r -> Math.PI * r * r
| Square sd -> sd * sd
let perimeter s =
match s with
| Circle r -> 2 * Math.PI * r
| Square sd -> sd * 4
Here is the moral equivalent in C#:
public abstract class Shape
{
public abstract float Area();
public abstract float Perimeter();
}
public sealed class Circle
{
readonly float r;
public Circle(float r)
{
this.r = r;
}
public override float Perimeter()
{
return Math.PI * 2 * r;
}
public override float Area()
{
return Math.PI * r * r;
}
}
public sealed class Square
{
readonly float side;
public Circle(float side)
{
this.side = side;
}
public override float Perimeter()
{
return 4 * side;
}
public override float Area()
{
return side * side;
}
}
A couple of things to note:
* The C# code needs 5 times as many line as the F# code (adding a new shape to F# will require about 3 lines, but the corresponding C# class definition will be at least 15 lines).
* The F# code is idiomatic, but hardly any C# programmer seals classes or defines them as immutable by default, even though it is a good practice.
That said, there are some advantages that C# enjoys. Consider our example code: Even though C# requires more lines of code to define a derivative of Shape, at a certain point it will always be easier to add new classes to C# than the corresponding variant to F# (see "The Expression Problem" - on the other hand, adding new functions that apply to Shape will be easier in F# than adding the corresponding new methods in C#). Also, F# is supposed to be a hardcore statically-typed language, but there are some advanced concepts (e.g. type classes) that are easy to realize in C# yet not in F#.
- CmonDev 12y agoI noticed you sealed the C# classes. What if you want to keep it extensible, which makes sense? How will that extensible pattern matching look then? Also would be interesting to see how long would C#6 code be (twice as short?). Apart from the sample, I also don't understand why F#'s naming patterns are not consistent with the rest of .NET (in terms of starting letter case). Feels alien. Also how come this was considered a sane syntax for arrays? [|1; 2; 3|] instead of [1, 2, 3] Even Haskell's syntax is less alien.
- noblethrasher 12y agoNot to mention indexers. Well, programming languages do have a prescriptive aspect. Arrays in C# 1.0 were more fundamental than List whereas the situation in F# 1.0 was the reverse, and F# doesn't really want you to use arrays as much as it wants you to use list. ... Personally, I always mark my classes as either sealed or abstract. In OO terms, I only care about extending the Shape class, not the individual subclasses because those themselves are are already extensions... of the Shape class. If I did want to extend, say, the Circle class, I would probably make a new abstract class called Ellipse that derived from Shape and then derive Circle from Ellipse. But, I don't really think in strict OO terms any more. C# has a decent type system (despite what my fellow ML programmers might say), but one of the mistakes of the language (i.e. the spec) is that it conflates types with classes. Those are different things. You may see three classes, but I see one type. In this case it's a sum type called Shape that ranges over Circle and Square. A sum type , as you may know, is just the type-theoretic version of logical OR. Indeed, it wasn't until I learned about sum types that I finally understood why you would use an abstract class as opposed to an interface: abstract classes are for when you want to say that a Foo is a Bar OR a Baz OR a Biz whereas interfaces are for when you want to say that Foo is a Bar AND a Baz, AND a Biz.
- CmonDev 12y ago> "In this case it's a sum type called Shape that ranges over Circle and Square" How do I extend Shape to include Triangle without modifying the current code (e.g. in a separate file)?
- noblethrasher 12y agoYou would have to modify the code, and as I mentioned, it can be pretty painful in F# and other ML languages if you already have a bunch of functions that apply to Shape. Subclassing is a good and useful tool that simply works better in C# than F#. But, just as importantly, class hierarchies are often a poor way to model a problem.
- 12y ago
- AndrewDucker 12y agoYeah, it looks a lot cleaner - but if you make that code available as a library then other people can't add new shapes easily, or deal with shapes that have more than one length (e.g. a rectangle, which has two).
- wwosik 12y agoThat comment about shapes with more than one defining size property is very valid.
- couchand 12y agoWell, yes, you can add a rectangle quite easily: type Shape = | Circle of float | Square of float | Rectangle of float * float The thought about adding new shapes (mentioned several times in this thread) is just misplaced. If you're releasing library code people should not be extending your built-in types, that's bad form -- consumers of a library should interact with it through interfaces only.
- ufo 12y agoThe flipside is that its very easy for your library users write a new function that takes a Shape as a parameter. If you model things with a class hierarchy its easy to add a new Shape subclass but you can't add any new methods to the Shape interface.
- wwosik 12y agoWell, C# idiomatic would be rather public interface IShape { float Area { get; } float Perimeter { get; } } public Circle : IShape { readonly float r ; public Circle(float r) { this.r = r; } public float Area { get { return Math.PI * r * r; } } public float Perimeter { get { return 2 * Math.PI * r; } } } public Square: IShape { readonly float a ; public Square(float a) { this.r = r; } public float Area { get { return a * a; } } public float Perimeter { get { return 4 * a; } } } Also, with this one you can create new Shapes in another project. With F# solution there, you must have access to original code.
- endeavour 12y agoYou've just described the expression problem. http://c2.com/cgi/wiki?ExpressionProblem http://c2.com/cgi/wiki?ExpressionProblem
- achr2 12y agoI actually think C# (and many OOP languages) gets around this fairly well, by having virtual and extension methods. Many types of functions can be added easily without needing to go back through every inheritor.
- louthy 12y ago* EDIT - realised I responded with an answer not related to the topic; I'll leave this here as a point-of-reference for the OO side of F# * Extension methods don't extend the type. They extend the functionality attached to the type. F# has these too [1] (actually more powerful versions): The thing about using sum-types (the original Shape example) is that they're not supposed to be extended. You're defining the range of the type, which allows for powerful inference of completeness when using them. If you need extensible classes then F# can do those too, it can represent interfaces, abstract classes and concrete classes [2][3][4], and can even generate ad-hoc classes from an interface by using Object Expressions [5]. I personally try to stick to the pure functional approach, but it can be very handy when interoperating with the .NET framework, or other C# based libraries. [1] http://fsharpforfunandprofit.com/posts/type-extensions/ http://fsharpforfunandprofit.com/posts/type-extensions/ [2] http://fsharpforfunandprofit.com/posts/classes/ http://fsharpforfunandprofit.com/posts/classes/ [3] http://fsharpforfunandprofit.com/posts/inheritance/ http://fsharpforfunandprofit.com/posts/inheritance/ [4] http://fsharpforfunandprofit.com/posts/interfaces/ http://fsharpforfunandprofit.com/posts/interfaces/ [5] http://fsharpforfunandprofit.com/posts/object-expressions/ http://fsharpforfunandprofit.com/posts/object-expressions/
- zer0nes 12y agoThis is a rather poor example. If you distribute the C# code above as a library, people can extend and introduce another shape, while they cannot do it easily with you F# code.
- noblethrasher 12y agoI did mention the Expression Problem[1] in my comment, and explicitly called out subclassing as a particular advantage of C# (the OP asked for criticism of F#). Also, even though F# can also subclass, that facility is a bit weak since it can't define protected members. [1]http://c2.com/cgi/wiki?ExpressionProblem http://c2.com/cgi/wiki?ExpressionProblem - funny thing is that the linked page coincidentally uses the exact same example code.
- louthy 12y agoYour F# example can be more concise: type Shape = | Circle of float | Square of float let area = function | Circle r -> Math.PI * r * r | Square sd -> sd * sd let perimeter = function | Circle r -> 2.0 * Math.PI * r | Square sd -> sd * 4.0
- noblethrasher 12y agoWell, that's just... neat.
- marxama 12y agoAnother thing is that the F# example has equality semantics built in, whereas in the C# example, you'd have to override Equals (and GetHashCode), adding more lines still (for some tedious work that you shouldn't have to do).