Почему пример честной функции в C# все еще не честен? - PullRequest
3 голосов
/ 03 апреля 2020

по этой ссылке: http://functionalprogrammingcsharp.com/honest-functions

Я научился быть более честным при определении метода / функции в C#. Он сказал, что предпочитает чистую функцию, чтобы функция всегда давала точный тип возврата, указанный в сигнатуре.

Однако, когда я пытаюсь применить его:

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

С веб-сайта:

Сигнатура утверждает, что функция принимает два целых числа и возвращает другое целое число. Но это не относится ко всем сценариям ios. Что произойдет, если мы вызовем функцию типа Divide (1, 0)? Реализация функции не соответствует своей подписи, вызывая исключение DivideByZero. Это означает, что эта функция также «нечестна». Как мы можем сделать эту функцию честной? Мы можем изменить тип параметра y (NonZeroInteger - это пользовательский тип, который может содержать любое целое число, кроме нуля):

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

Я не уверен, что является реализацией NonZeroInteger, они не Кажется, что не дает какой-либо реализации NonZeroInteger на веб-сайте, должен ли он проверять на 0 внутри этого класса? И я уверен, что если я вызову Divide (1, null), он все равно покажет ошибку, что сделает функцию не честной.

Почему пример честной функции в C# все еще не честен?

Ответы [ 6 ]

4 голосов
/ 03 апреля 2020

Взяв пример, который вы опубликовали, и прочитав ссылку, если вы хотите сделать функцию "честной", тогда вам не нужно создавать новый тип, вы можете просто реализовать шаблон Try:

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

  result = 0;
  return false;
}

Эта функция в основном соответствует «честному» принципу. Название говорит, что оно попытается выполнить деление, а полученный «bool» говорит, что он будет указывать, что он был успешным.

Вы можете создать struct NonZeroInteger, но вам придется много писать кода вокруг него, чтобы он действовал как обычный числовой тип c, и вы, вероятно, пройдете полный круг. Например, что если вы передадите 0 конструктору NonZeroInteger? Должно ли это потерпеть неудачу? Это правда.

Кроме того, тип struct всегда имеет конструктор по умолчанию, поэтому, если вы обертываете int, будет неудобно избегать установки его в 0.

2 голосов
/ 03 апреля 2020

Если честно, определите новую структуру данных и проверьте статус.

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);
    }
}
2 голосов
/ 03 апреля 2020

Понятие "честной функции" все еще имеет место для интерпретации, и я не хочу спорить об этом здесь, было бы больше мнения, чем фактического полезного ответа.

Чтобы конкретно ответить на ваш пример, вы может объявить NonZeroInteger как ValueType с struct вместо class.

Тип значения не может иметь значение NULL (кроме случаев, когда вы явно указываете NULL-версию с ?). Нет нулевой проблемы в этом случае. Кстати, int является примером типа значения (точнее, псевдонимом для System.Int32).

Поскольку у некоторых указано , это может привести к другим трудности (структура всегда имеет конструктор по умолчанию, который инициализирует все поля по умолчанию, а для int по умолчанию задано значение 0 ...)

Для программистов среднего уровня этот пример не нужен быть явно реализованным в статье, чтобы быть понятным в принципе.

Однако, если вы не уверены в этом, это определенно будет хорошим обучающим упражнением по программированию, я настоятельно рекомендую вам реализовать его самостоятельно! (И, между прочим, создайте модульные тесты, чтобы продемонстрировать, что в вашей функции нет «ошибки»)

1 голос
/ 03 апреля 2020

Этот NonZeroInteger является просто «символом», который просто представляет идею, а не конкретную реализацию.

Конечно, автор мог бы обеспечить реализацию такой конструкции, но ее серверы имен как раз ради article.

Возможная реализация может быть такой:

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

Но это просто подталкивает нечестность в другом месте (в терминах статьи), потому что конструктор должен возвращать объект, а не генерировать исключение.

ИМО, честность недостижима, потому что она просто движет нечестностью куда-то еще, как показано в этом примере.

0 голосов
/ 03 апреля 2020

После прочтения много раз ..

Я обнаружил, что его второй вариант на сайте честный, а первый неправильный.

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

Редактировать: получил идея из другой статьи, в основном имитирующая путь F #, что-то вроде этого:

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/

для вызова:

public void Main() {
    Option<int> result = Divide(1,0);
    result.Match(
        x => Console.Log(x),
        () => Console.Log("divided by zero")
    )
}
0 голосов
/ 03 апреля 2020

Честно говоря, это, IMO, излишне, но если бы я использовал "Честный" метод. Я бы сделал что-то подобное. Вместо создания целого нового класса. Это не рекомендация того, что делать, и может легко вызвать проблемы в вашем коде позже. Наилучший способ справиться с этим, IMO, - использовать функцию и перехватить исключение за пределами метода. Это «честный» метод, поскольку он всегда возвращает целое число, но может возвращать ложные значения обратно.

    int Divide(int x, int y)
    {
        try
        {
            return x / y;
        }
        catch (DivideByZeroException)
        {
            return 0;
        }
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...