Может быть, класс и дополнительные параметры

У меня есть реализация класса Maybe / Option в C#. Базовая реализация

public delegate Maybe<TOutput> Converter<in TInput, out TOutput>(TInput input);
public delegate TOutput ElseDelegate<out TOutput>();
public delegate Maybe<TOutput> ElseDelegate2<out TOutput>();

public interface Maybe<out TResult> : IEnumerable<TResult>
{
    Maybe<B> Bind<B>(Converter<TResult, B> f);
    TResult Value();
    bool IsSome();
}

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

public interface INone<out TResult> : Maybe<TResult>
{
}

public interface ISome<out TResult> : Maybe<TResult>
{
}

public struct None<TResult> : INone<TResult>
{

    public IEnumerator<TResult> GetEnumerator()
    { yield break; }

    IEnumerator IEnumerable.GetEnumerator()
    { yield break; }


    public bool IsSome() { return false; }

    public Maybe<TOutput> Bind<TOutput>(Converter<TResult, TOutput> f)
    {
        return new None<TOutput>();
    }

    public TResult Value()
    {
        throw new IndexOutOfRangeException("None has no value");
    }
}

public struct Some<TResult> : Maybe<TResult>
{
    private TResult _Value;
    public Some(TResult value)
    {
        _Value = value;
    }

    public IEnumerator<TResult> GetEnumerator()
    { yield return _Value; }

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

    public bool IsSome() { return true; }

    public Maybe<TOutput> Bind<TOutput>(Converter<TResult, TOutput> f)
    {
        return f(_Value);
    }

    public TResult Value()
    {
        return this._Value;
    }
}
#endregion

с кучей методов расширения я здесь не включил. Это все отлично работает. Однако стандартный шаблон, который я хотел бы реализовать, приведен ниже, используя Maybe для реализации необязательных параметров по умолчанию, как в F#

void DoSomeCalc
    ( Maybe<double> x = Maybe.None<double>()
    , Maybe<double> y = Maybe.None<double>()
    )
{
    this.X = x.Else( ()=> CalculateDefaultX() );
    this.Y = y.Else( ()=> CalculateDefaultY() );
}

так что я могу сделать

DoSomeCalc(x:10)

или же

DoSomeCalc(y:20)

где Else предоставляет значение, если None недоступен. Тем не менее, это все хорошо в теории, но необязательные параметры C# должны быть константами времени компиляции, которые полностью портят этот шаблон.

Кто-нибудь может предложить исправление, которое сохранит намерение шаблона, не вводя здесь nullables или null?

Могу ли я в любом случае создать постоянную времени компиляции для представления None, которая будет работать с моей приведенной выше реализацией Maybe?

2 ответа

Нет, ты ничего не можешь сделать здесь. Ваш тип параметра является ссылочным типом, что означает, что доступны только постоянные значения null и строковые литералы. (Очевидно, строковые литералы бесполезны в вашем случае; я упоминаю их только как единственный вид константы ненулевого ссылочного типа.)

Одним из вариантов будет сделать Maybe<T> структура вместо интерфейса со значением по умолчанию, равным "нет". Тогда это будет в основном так же, как Nullable<T> но без ограничения, что T должен был быть необнуляемым типом значения. Затем вы можете использовать:

void DoSomeCalc(Maybe<double> x = default(Maybe<double>),
                Maybe<double> y = default(Maybe<double>))

Пример кода, показывающий все это:

using System;

struct Maybe<T>
{
    private readonly bool hasValue;
    public bool HasValue { get { return hasValue; } }

    private readonly T value;
    public T Value
    {
        get
        {
            if (!hasValue)
            {
                throw new InvalidOperationException();
            }
            return value;
        }
    }

    public Maybe(T value)
    {
        this.hasValue = true;
        this.value = value;
    }

    public static implicit operator Maybe<T>(T value)
    {
        return new Maybe<T>(value);
    }
}

class Test
{
    static void DoSomeCalc(Maybe<double> x = default(Maybe<double>),
                           Maybe<double> y = default(Maybe<double>))
    {
        Console.WriteLine(x.HasValue ? "x = " + x.Value : "No x");
        Console.WriteLine(y.HasValue ? "y = " + y.Value : "No y");
    }

    static void Main()
    {
        Console.WriteLine("First call");
        DoSomeCalc(x: 10);

        Console.WriteLine("Second call");
        DoSomeCalc(y: 20);
    }
}

Очевидно, вы хотите добавить больше функциональности к Maybe<T> например переопределение ToString а также Equals, но вы получите общее представление. Вы все еще можете иметь неуниверсальный Maybe Конечно, класс с фабричными методами.

Вы можете использовать null для обозначения Maybe.None(). Например:

double DoSomeCalc
    ( Maybe<double> x = null
    , Maybe<double> y = null
    )
{
    x = x ?? Maybe.None<double>();
    y = y ?? Maybe.None<double>();
    this.X = x.Else( ()=> CalculateDefaultX() );
    this.Y = y.Else( ()=> CalculateDefaultY() );
}

Это не идеально, так как вы должны документировать где-то в комментариях, что передача нуля означает "использовать определенное значение по умолчанию".

Другие вопросы по тегам