Why honest function example in C# still not being honest?

Viewed 438

from this reference : http://functionalprogrammingcsharp.com/honest-functions

I have learned to be more honest when defining method/function in C#. It said that prefer pure function so that the function will always give the exact return type given in signature.

However when I try to apply it:

int Divide(int x, int y)
{
    return x / y;
}

From the website:

The signature states that the function accepts two integers and returns another integer. But this is not the case in all scenarios. What happens if we invoke the function like Divide(1, 0)? The function implementation doesn't abide by its signature, throwing DivideByZero exception. That means this function is also "dishonest". How can we make this function an honest one? We can change the type of the y parameter (NonZeroInteger is a custom type which can contain any integer except zero):

int Divide(int x, NonZeroInteger y)
{
    return x / y.Value;
}

I'm not sure what is the implementation of NonZeroInteger, they don't seem to give any implementation of NonZeroInteger in the website, should it check for 0 inside that class? And I'm pretty sure if I call Divide(1, null) it will still show an error, thus making the function not honest.

Why honest function example in C# still not being honest?

7 Answers

Taking the example you've posted, and having read the link, if you want to make the function "honest" then you don't really need to create a new type, you could just implement the Try pattern:

bool TryDivide(int x, int y, out int result)
{
  if(y != 0)
  {
    result = x / y;
    return true;
  }

  result = 0;
  return false;
}

This function basically fulfills the "honest" principle. The name says it will try to do division, and the resulting 'bool` says that it will indicate it is was successful.

You could create a struct NonZeroInteger but you're going to have to write a lot of code around it to make it act like a regular numeric type, and you'll probably come full circle. For example, what if you pass 0 to the NonZeroInteger constructor? Should it fail? Is that honest.

Also, struct type always have a default constructor, so if you're wrapping an int it's going to be awkward to avoid it being set to 0.

The notion of "honest function" still has room for interpretation, and I don't want to debate about it here, would be more opinion than actual useful answer.

To specifically answer your example, you could declare NonZeroInteger as a ValueType, with struct instead of class.

A value type is non-nullable (except if you explicitly specify the nullable version with a ?). No null-problem in this case. By the way, int is an example of value type (it's an alias for System.Int32, to be exact).

As some have pointed out, it could lead to other difficulties (struct has always a default constructor that initialize all fields to their default, and the default for an int is 0...)

For an mid-experienced programmer, this kind of example does not need to be explicitly implemented in the article to be understood on principle.

However, if you are unsure about it, it would definitely be a good programming learning exercise, I strongly encourage you to implement it yourself! (And create unit tests to demonstrate that your function has no "bug", by the way)

To make it honest, define a new data structure and check the status.

enum Status { OK, NAN }
class Data
{
    public int Value { get; set; }
    public Status Status { get; set; }

    public static Data operator /(Data l, Data r)
    {
        if (r.Value == 0)
        {
            // Value  can be set to any number, here I choose 0. 
            return new Data { Value = 0, Status = Status.NAN };
        }
        return new Data { Value = l.Value / r.Value, Status = Status.OK };
    }

    public override string ToString()
    {
        return $"Value: {Value}, Status: {Enum.GetName(Status.GetType(), Status)}";
    }
}

class Test
{
    static Data Divide(Data left, Data right)
    {
        return left / right;
    }
    static void Main()
    {
        Data left = new Data { Value = 1 };
        Data right = new Data { Value = 0 };
        Data output = Divide(left, right);

        Console.WriteLine(output);
    }
}

This NonZeroInteger is just a "symbol", which just represents the idea, not conrete implementation.

Surely, author could provide implemenetation of such construct, but its name servers just right for the sake of an article.

Possible implememntation might be:

public class NonZeroInteger
{
  public int Value { get; set; }
  public NonZeroInteger(int value)
  {
    if( value == 0 ) throw new ArgumentException("Argument passed is zero!");
    Value = value;
  }
}

But it's just pushing dishonesty somewhere else (in terms of an article), because constructor should return an object, not throw exception.

IMO, honesty is not achievable, because it's just moving dishonesty somewhere else, as shown in this example.

After reading it thoroughly a lot of times..

I found that his second option on the website is honest, and the first one is wrong.

int? Divide(int x, int y)
    {
        if (y == 0)
        return null;
        return x / y;
    }

Edit: got idea from another article, basically mimicking the F# path, something like this:

Option<int> Divide(int x, int y)
    {
        if (y == 0)
            return Option<int>.CreateEmpty();
        return Option<int>.Create(x / y);
    }

public class Option<T> : IEnumerable<T>
{
    private readonly T[] _data;

    private Option(T[] data)
    {
        _data = data;
    }

    public static Option<T> Create(T element)
    {
        return new Option<T>(new T[] { element });
    }

    public static Option<T> CreateEmpty()
    {
        return new Option<T>(new T[0]);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>) _data).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Match(Action<T> onSuccess, Action onError) {
        if(_data.Length == 0) {
            onError();
        } else {
            onSuccess(_data[0]);
        }
    }
}


Ref: https://www.matheus.ro/2017/09/26/design-patterns-and-practices-in-net-option-functional-type-in-csharp/

to call:

public void Main() {
    Option<int> result = Divide(1,0);
    result.Match(
        x => Console.Log(x),
        () => Console.Log("divided by zero")
    )
}

I just want to mention that NonZeroInteger can definitely be implemented honestly using a variation on Peano numbers:

class NonZeroInteger
{
    /// <summary>
    /// Creates a non-zero integer with the given value.
    /// (This is private, so you don't have to worry about
    /// anyone passing in 0.)
    /// </summary>
    private NonZeroInteger(int value)
    {
        _value = value;
    }

    /// <summary>
    /// Value of this instance as plain integer.
    /// </summary>
    public int Value
    {
        get { return _value; }
    }
    private readonly int _value;

    public static NonZeroInteger PositiveOne = new NonZeroInteger(1);
    public static NonZeroInteger NegativeOne = new NonZeroInteger(-1);

    /// <summary>
    /// Answers a new non-zero integer with a magnitude that is
    /// one greater than this instance.
    /// </summary>
    public NonZeroInteger Increment()
    {
        var newValue = _value > 0
            ? _value + 1    // positive number gets more positive
            : _value - 1;   // negative number gets more negative
        return new NonZeroInteger(newValue);   // can never be 0
    }
}

The only tricky part is that I've defined Increment so that it works with both positive and negative integers. You can create any integer value you want except zero, and no exceptions are ever thrown, so this class is totally honest. (I'm ignoring overflow for now, but I don't think it would be a problem.)

Yes, it requires you to increment by one repeatedly to build large integers, which is extremely inefficient, but that's OK for a toy class like this one. There are probably other honest implementations that would be more efficient (e.g. using a uint as an offset from +1 or -1), but I'll leave that as an exercise for the reader.

You can test it like this:

class Test
{
    static int Divide(int x, NonZeroInteger y)
    {
        return x / y.Value;
    }

    static void Main()
    {
        var posThree = NonZeroInteger.PositiveOne
            .Increment()
            .Increment();
        Console.WriteLine(Divide(7, posThree));

        var negThree = NonZeroInteger.NegativeOne
            .Increment()
            .Increment();
        Console.WriteLine(Divide(7, negThree));
    }
}

Output is:

2
-2

Honestly this is, IMO, overkill, but if I were to do a "Honest" method. I would do something like this. Instead of creating an entire new class. This is not a recommendation of what to do, and could easily cause issues in your code later. The best way to handle this, IMO, is to use the function and catch the exception outside of the Method. This is an "Honest" method in the fact that it always returns an integer, but it could return false values back.

    int Divide(int x, int y)
    {
        try
        {
            return x / y;
        }
        catch (DivideByZeroException)
        {
            return 0;
        }
    }
Related