1. Why does F# allow me to override these operators if it doesn't honor them?

2. Why is the > operator bound to the IComparable interface rather than the op_GreatorThan method?

3. Why doesn't the > operator result in a compiler error when IComparable isn't implemented by the class?

(I try to point out some facts, but a bit of this also expresses my opinions.)

1. F# does honor them, if you call them. For example

 let x = OppTest4(10)
let b = OppTest4.op_GreaterThan(x,x)

works fine. As for the 'global' (>) operator, it's just another let-bound function name, which can be bound to anything. The default value of this operator has a different meaning... which leads to

2. Sure, F# could bind to op_GreaterThan by default, but why do languages define op_GreaterThan? For syntactic support. None of the generic algorithms in .Net use op_GreaterThan definitions -- Array.Sort(), to take one example of many, uses IComparable for sorting. The whole op_GreaterThan thing is just a way that languages like C# and VB choose to support the 'sugar' of saying x > y rather than any of the more verbose alternatives (e.g. x.CompareTo(y)>0, x.IsGreaterThan(y), X.op_GreaterThan(x,y), or whatever else you might imagine). The 95% scenario for 'wanting the syntactic sugar' is to 'put it in sync with the IComparable implementation', since this is the natural semantic people tend to expect. In F#, all you have to do is have your type implement IComparable and you're done, you get (>) for free. It also means you can author generic algorithms that contain code like "x > y" rather than the more verbose "x.CompareTo(y)>0". Now this is quite clearly a trade-off; in the 5% scenario where you desire the sugar to mean something else, you have to redefine the operator. Regardless of the scenario, in either case, if you want to export functionality to other languages, F# lets you do it (and uses the standard names suggested by the ECMA spec). I think the F# choice here is pragmatic in that it caters to the common case without forcing you to write lots of extra boilerplate, but there are two sides to this coin, and being 'more standard' here and having (>) mean op_GreaterThan is a reasonable alternative in the design space, but it's not the one F# chose. (And if the CLR directly supported a type-class-like mechanism, perhaps the whole discussion would be moot... but that's another story.)

3. This is a very fair criticism, and it's possible that we'll implement more static checking here in Beta2 (though it probably would require new special rules in the F# type system to deal with this one case). The troubles here stem from a few different sources; I'll call out two. First, I think the tradition of functional languages with algebraic data types expects that such types support structural equality and comparison operators. That is, for example, I can compare two lists, or tuples, or whatnot, and a lexicographical comparison will be used (e.g. [1;2] > [1;1], or (4,"a") > (3,"b"), ...). (This is very handy, BTW, see e.g. the HandValuation type I wrote about in a blog.) Now the problem is, should list<T> be IComparable? Well, if T is IComparable, then we want the list to be comparable, but if T is not, then we don't. But the CLR static type system can't express this directly (once again, the CLR is the weak link). (There are ways to express thse kinds of static constraints within the limits of the CLR, but they require an alternative API design and are a bit of a mess for something simple.) So, the remaining choices are, we can abandon IComparable for algebraic data types, or we can always implement IComparable but have the implementation fail at runtime if the underlying types are not comparable. F# chooses the latter. (Notably, the .Net 4.0 System.Tuple<...> classes appear to also take this strategy, implementing IComparable that fails at runtime for e.g. System.Tuple<NonComparableType,int>.) Anyway, back to my two reasons. Second, F# allows some non-IComparable types (like arrays and IntPtrs) to be compared with (>) with the expected semantics. So these instances would violate the type system if (>) were only defined forall.T where T is IComparable. So those are two 'reasons' that are at least suggestive of the justification for the state of affairs. I think there are more, but that's a flavor of the complexity of the problem.

That only covers about a third of what I have to say on the topic, but I'm already tired of writing for now.
By on 6/9/2009 10:01 PM ()
Thanks, Brian, for this nice explanation!
By on 6/9/2009 10:19 PM ()
encountered someting simular I defined

let (+) (x y z) (vx vy vz) = (x + vx, y + vy, z + vz)

and found it worked fine.

however shortly therafter

cube.[x,y,x] <- x * 16 + y * 4 + 1

failed after defining my (+), so I changed it to Add.

glancing at this post, I reckon F# has some typechecking issues still(?) as adding two tuples or adding two integers are two entirely different things in my book.

By on 6/9/2009 11:04 PM ()
The .NET CLS (Common Language Specification) doesn't really address operators, so you should expect different languages to handle some cases differently.

1. F# will allow you to create members with whatever names you want. Note that it's not quite accurate to say that you're "overrid[ing] these operators" - there's no overriding going on at all. You're creating members with particular names, and operators in F#, C#, and VB.NET rely on members with specific funny names to define overloaded operators (although classes like int, string, etc. may be handled specially even if they don't have such members). I'd argue that it's good that F# allows you to create classes that contain members that can be used idiomatically from other languages (such as concatenation in VB.NET), even if they can't be used in F#. It’s pretty easy to see which built-in operators can be overloaded (see the documentation for the Microsoft.FSharp.Core.Operators module). Even here, there are some subtleties, though – (**), (&&&), etc. take the same type on the left and right, whereas (+), (/), etc. can have different types on the left and right (and can return yet another type).

2/3. I can only speculate on the reasons, but comparison is a really tricky/annoying area which would probably best be addressed by something more like type classes, which .NET's type system doesn't support. I imagine that one complication for F# is that it provides automatic structural comparison for most F# types (such as discriminated unions, etc.). Defining a comparer for these types may require a comparer for the components that make up the type (elements in a list, for example), which would make the op_GreaterThan approach tricky.

Consider the list type: to compare two lists, you need to be able to compare the items those lists contain. On the other hand, you’d like to be able to store items of any type in a list, since you won’t always need comparisons. This is where typeclasses would be handy. Since they’re not available, F# allows you to compare two ‘a list objects regardless of the type ‘a, which unfortunately means that there can be runtime errors if ‘a itself doesn’t have a defined comparison function. Basing comparison on an op_GreaterThan method would be difficult at best, because you don’t know ahead of time what type to look at to find the method (e.g. for a list<TypeA> and a list<TypeB>, you would need to use different static methods which aren't known when the list type is compiled). On the other hand, with the interface approach, you can attempt to upcast an item to the IComparable interface and call the CompareTo method.

Section 9.6 of the spec ([link:research.microsoft.com]) goes into some good detail about a few of the issues involved here, and indicates that a future version of the compiler may give warnings when comparison operators are called at types where any non-null values are known to lead to run-time exceptions.
By on 6/9/2009 11:50 AM ()
> The .NET CLS (Common Language Specification) doesn't really address operators, so you should expect different languages to handle some cases differently.

Yes it does. It is in section 10.3 - Operator Overloading. In fact, 'op_GreaterThan' is the first word on page 55.

[link:www.ecma-international.org]

> It’s pretty easy to see which built-in operators can be overloaded

Not in the documentation. That really needs to be fixed.

[link:msdn.microsoft.com]

> Defining a comparer for these types [...] would make the op_GreaterThan approach tricky.

> On the other hand, with the interface approach, you can attempt to upcast an item to the IComparable interface and call the CompareTo method.

I find these statements to be at odds.

You can always define op_GreaterThan in terms of CompareTo. Likewise you can always define CompareTo in terms of op_GreaterThan and op_LessThan.

Therefore, F# should only support one directly and the compiler should implicitly emit the other automatically.

By on 6/9/2009 4:04 PM ()

The .NET CLS (Common Language Specification) doesn't really address operators, so you should expect different languages to handle some cases differently.

Yes it does. It is in section 10.3 - Operator Overloading. In fact, 'op_GreaterThan' is the first word on page 55.

[link:www.ecma-international.org]


Sorry, you're right. However, the beginning of that section also mentions:

"CLS-compliant consumer and extender tools are under no obligation to allow defining of operator overloading.

CLS-compliant consumer and extender tools do not have to provide a special mechanism to call these methods."

> It’s pretty easy to see which built-in operators can be overloaded

Not in the documentation. That really needs to be fixed.

[link:msdn.microsoft.com]

You're right that the MSDN documentation isn't particularly complete. However, I think that the F# library documentation (installed with the rest of F#) presents a better picture here. I'd hope that the MSDN documentation will become more complete as the RTM of VS 2010 approaches.

> Defining a comparer for these types [...] would make the op_GreaterThan approach tricky.

> On the other hand, with the interface approach, you can attempt to upcast an item to the IComparable interface and call the CompareTo method.

I find these statements to be at odds.

You can always define op_GreaterThan in terms of CompareTo. Likewise you can always define CompareTo in terms of op_GreaterThan and op_LessThan.

Therefore, F# should only support one directly and the compiler should implicitly emit the other automatically.

It's possible for the F# compiler to generate an op_GreaterThan static member, but there's no way to actually call it in all cases, like the list example I initially mentioned. How would you write the comparison method on the (generic) list type so that it in turn relies on the comparison method of its parameter?
By on 6/9/2009 8:37 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us
Built with WebSharper