tempusfugit (130) [Avatar] Offline
#1
If you have some time I'd like to hear what motivated your design decision to use the OptionType Tag and the associated conditional logic in the C# Option implementation. Seeing the "Tag" made my "refactoring sense tingle" , in particular
* Replace Type Code with Subclasses
* Replace conditional with Polymorphism

In particular, are there any shortcomings in the following alternative implementation that I am overlooking?

[pre]
using System;

namespace AltOption {

public abstract class Option<T> {
protected Option() {}

public abstract bool MatchNone();
public abstract bool MatchSome(out T value);
public abstract Option<R> Map<R>(Func<T, R> f );
public abstract Option<R> Bind<R>(Func<T, Option><R>> f);
}

public class None<T> : Option<T> {

public override bool MatchNone() { return true; }

public override bool MatchSome(out T value) {
value = default(T);
return false;
}

public override Option<R> Map<R>( Func<T, R> f ) {
return new None<R>();
}

public override Option<R> Bind<R>( Func<T, Option><R>> f) {
return new None<R>();
}
}

public class Some<T> : Option<T> {
public Some(T value) {
Value = value;
}

public T Value { get; private set; }

public override bool MatchNone() { return false; }

public override bool MatchSome(out T value) {
value = Value;
return true;
}

public override Option<R> Map<R>( Func<T, R> f ) {
return new Some<R>(f(Value));
}

public override Option<R> Bind<R>( Func<T, Option><R>> f ){
return f(Value);
}
}

public static class Option {

public static Option<T> None<T>() {
return new None<T>();
}

public static Option<T> Some<T>(T value) {
return new Some<T>(value);
}
}
}
[/pre]

Thank You.
Tomas Petricek (160) [Avatar] Offline
#2
Re: Ch 5,6 C# Option implementation (Design Question)
Hi,
this is a difficult question. In this case, your implementation is perfectly correct and it is definitely better, because it follows the object-oriented/C# coding standards.

The book uses an alternative style, because it more closely resembles the F# types and I was hoping that this will make it easier to understand how things work in F# (I'm writing a note that I should explicitly say that in the book, thanks!).

The most important thing is that once you have "MatchNone" and "MatchSome" you can keep "Map" and "Bind" as extension methods (because they can be implemented using the two Match methods). I'd prefer that because there are other operations that you may want to do with option values and in the functional desing you'd probably add/remove them more often.

From the strictly object-oriented point of view, "MatchNone" and "MatchSome" are wrong just like type codes. These methods fully reveal the structure of the class hierarchy (just like Tag) and it makes it impossible to add a new subclass without modifying the base class (and adding "MatchNewSubClass" method). However, the point of discriminated unions in functional programming is that they make it easy to add new operations, which is possible only when we reveal the whole class hierarchy.

However, "discriminated unions" are probably the only thing that conflicts with any object-oriented guidelines. Otherwise the functional and object-oriented styles play very nicely together and I guess the when writing functional code in C# you just won't use "discriminated unions" (encoded in C#) as often as you'd use them in F#.

Hope this clarifies the point and thanks for asking the question - I'll try to add some explanation to the manuscript as well.

Tomas
tempusfugit (130) [Avatar] Offline
#3
Re: Ch 5,6 C# Option implementation (Design Question)
"The book uses an alternative style, because it more closely resembles the F# types and I was hoping that this will make it easier to understand how things work in F#."

I suspected that the reason might be "educational", i.e. to bridge the gap and make the C# code more like F# code. I think its a good idea to add that note - while it's important to leverage a functional style in C#, it is also important to exploit the various strengths of the different CLR languages - that is why it is meaningful to support them all. In the case of C# "Replace conditional with polymorphism" should be a preferred approach in most cases.

"The most important thing is that once you have "MatchNone" and "MatchSome" you can keep "Map" and "Bind" as extension methods (because they can be implemented using the two Match methods)."

Given the context you only need "IsNone" which is functionally equivalent to "IsEmpty" (which Scala complemented with a getOrElse method). Admittedly I currently don't understand why it is preferable to keep "Map" and "Bind" as extension rather than instance methods - unless it is simply the opportunity for adding similar functions later without the need to re-compile existing client objects.

"'discriminated unions' are probably the only thing that conflicts with any object-oriented guidelines".
Being guidelines the term conflict might be a bit harsh. In cases like Option, Option/None/Some implement one cohesive concept together - each class in isolation doesn't make much sense on its own. Personally I think it is permissible to reveal the whole class hierarchy in such a case, especially as no additional derived classes are expected to be added later.

Thank You for taking the time to respond!
Tomas Petricek (160) [Avatar] Offline
#4
Re: Ch 5,6 C# Option implementation (Design Question)
Hi, thanks again for your reply.

I was just reviewing the chapter 5, so I added a brief sidebar explaining the motivation for this design (based on my previous post here). I believe it'll be helpful to readers who are wondering the same thing as you!

>> why it is preferable to keep "Map" and "Bind" as extension
>> rather than instance methods - unless it is simply the opportunity
>> for adding similar functions later without the need to re-compile
>> existing client objects

My motivation was mainly to follow the functional style in F# (and functional programming). I think the motivation for this in F# is the ability to add similar processing functions. In fact, the F# libraries have about 13 methods for procesing option values, but only a part of them are in the core F# library. The rest is located in PowerPack, so they benefit from this design choice.

Anyway, thanks again for your (very useful) feedback!
Tomas