Как: замкнутый инвертированный троичный оператор, например, в C #? Это имеет значение? - PullRequest
3 голосов
/ 05 августа 2009

Предположим, что вы используете тернарный оператор или оператор объединения нулей, или вложенные операторы if-else для выбора назначения объекту. Теперь предположим, что в условном выражении у вас есть оценка дорогостоящей или изменчивой операции, требующая, чтобы вы поместили результат во временную переменную, зафиксировав его состояние, чтобы его можно было сравнить, а затем потенциально назначить.

Как бы язык, такой как C #, для рассмотрения реализовал бы новый логический оператор для обработки этого случая? Должно ли это? Существуют ли способы справиться с этим делом в C #? Другие языки?

Некоторые случаи снижения многословности троичного или нулевого оператора объединения были преодолены, когда мы предполагаем, что, например, мы ищем прямые сравнения. См. Уникальные способы использования оператора Null Coalescing , в частности обсуждение того, как можно расширить использование оператора для поддержки String.IsNullOrEmpty(string). Обратите внимание, как Джон Скит использует PartialComparer с MiscUtil, чтобы переформатировать 0 с null с,

Почему это возможно необходимо? Что ж, взгляните на то, как мы пишем метод сравнения для сложных объектов без каких-либо ярлыков (примеры из цитируемых обсуждений):

public static int Compare( Person p1, Person p2 )
{
    return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
        : ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
        : Compare( p1.Salary, p2.Salary );
}

Джон Скит пишет новое сравнение для отступления от случая равенства. Это позволяет расширить выражение, написав новый специальный метод, который возвращает нуль, что позволяет нам использовать оператор объединения нуль:

return PartialComparer.Compare(p1.Age, p2.Age)
    ?? PartialComparer.Compare(p1.Name, p2.Name)
    ?? PartialComparer.Compare(p1.Salary, p2.Salary)
    ?? 0;

Нулевой оператор объединения более читабелен, поскольку имеет две стороны, а не три. Логическое условие условие разделено на метод, в этом случае возвращающее null, если выражение должно быть продолжено.

Как бы выглядело вышеприведенное выражение, если бы мы могли проще поместить условие в строку? Возьмите выражение из PartialComparer.Compare, которое возвращает null, и поместите его в новое троичное выражение, которое позволяет нам использовать оценку левого выражения с неявной временной переменной value:

return Compare( p1.Age, p2.Age ) unless value == 0
     : Compare( p1.Name, p2.Name ) unless value == 0
     : Compare( p1.Salary, p2.Salary );

Основной "поток" выражения будет:

выражение A , если логическое B , в этом случае выражение C

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

  • Будет ли полезен этот тип логики? В настоящее время нулевое объединение предоставляет нам способ сделать это с помощью условного выражения (value == null).
  • Какие еще выражения вы бы хотели проверить? Мы слышали о (String.IsNullOrEmpty(value)).
  • Как лучше всего выразить это на языке, в терминах операторов, ключевых слов?

Ответы [ 5 ]

5 голосов
/ 06 августа 2009

лично я бы избежал короткого замыкания от операторов и просто позволил бы методам связать его:

public static int CompareChain<T>(this int previous, T a, T b)
{
    if (previous != 0)
        return previous;
    return Comparer<T>.Default.Compare(a,b);
}

используйте так:

int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);

может быть встроен в JIT, поэтому он может выполнять такие же короткие замыкания, что и встроенный в язык, без лишних сложностей.

В ответ на ваш вопрос, может ли указанная выше «структура» применяться не только к сравнениям, но да, это может быть сделано путем выбора: продолжать или нет объяснять и контролировать пользователь. Это по своей сути более сложное, но операция более гибкая, поэтому это неизбежно.

public static T ElseIf<T>(
    this T previous, 
    Func<T,bool> isOK
    Func<T> candidate)
{
    if (previous != null && isOK(previous))
        return previous;
    return candidate();
}

затем используйте так

Connection bestConnection = server1.GetConnection()
    .ElseIf(IsOk, server2.GetConnection)
    .ElseIf(IsOk, server3.GetConnection)
    .ElseIf(IsOk, () => null);

Это максимальная гибкость, поскольку вы можете изменить проверку IsOk на любом этапе и быть полностью ленивыми. Для ситуаций, когда проверка «ОК» одинакова в каждом случае, вы можете упростить, как и так, и полностью избежать методов расширений.

public static T ElseIf<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   throw new ArgumentException("none were acceptable");
}

Вы можете сделать это с помощью linq, но этот способ выдает красивое сообщение об ошибке и позволяет это

public static T ElseIf<T>(        
    Func<T,bool> isOK
    params Func<T>[] candidates)
{
    return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}

стиль, который приводит к хорошему читабельному коду, например:

var bestConnection = ElseIf(IsOk,
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);

Если вы хотите разрешить значение по умолчанию, то:

public static T ElseIfOrDefault<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   return default(T);
}

Очевидно, что все вышеперечисленное можно очень легко написать с помощью лямбд, поэтому ваш конкретный пример будет:

var bestConnection = ElseIfOrDefault(
    c => c != null && !(c.IsBusy || c.IsFull),
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);
2 голосов
/ 30 октября 2018

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

На самом деле C # имеет этот оператор, но только в запросах . Хотелось бы, чтобы мы смогли добавить это как оператор в C # 3:

public static int Compare(Person p1, Person p2) =>
  let ages = Compare(p1.Age, p2.Age) in
    ages != 0 ? 
      ages : 
      let names = Compare(p1.Name, p2.Name) in
      names != 0 ? 
        names : 
        Compare(p1.Salary, p2.Salary);

«Позвольте выражениям» - это одно из тех выражений, которые настолько полезны и встречаются в очень немногих языках , и я искренне не понимаю, почему разработчики языка не добавляют его сразу в первой версии.

Если в C # была эта функция, то вы предлагали:

A() unless B() : C()

это просто

let a = A() in B() ? C() : a

, который чуть сложнее понять, и бонус, вы можете использовать a в выражениях B() и C(), если хотите.

Пусть выражения можно эмулировать на любом языке с лямбдами; конечно, let x = y in z - это просто (x=>z)(y), но в C # нет четкого способа написать это, потому что C # требует преобразования в тип делегата в каждой лямбде.

Кстати, в Рослине мы не представляем временные выражения как выражения для выражения, хотя мы могли бы. Скорее, мы идем даже на один уровень ниже и имеем представление для «последовательности операций, которые могут создавать значения, одна из которых станет значением этого выражения». «let x = y in z» - это просто последовательность «allocate x, x = y, z, освобождает x», где третий элемент - это значение. И в оригинальном пре-розлинном компиляторе C # у нас были внутренние операторы «left» и «right», которые были бинарными операторами, которые принимали два выражения и производили либо левую, либо правую сторону, поэтому мы могли генерировать ((allocate x) right ((x = y) right z)) left (deallocate x).

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

0 голосов
/ 07 августа 2009

More @ ShuggyCoUk : А, я вижу, что это может сработать не только для сравнения? Я не использовал C # 3 и методы расширения, но я полагаю, что вы можете объявить, для моего предыдущего примера ниже,

public delegate bool Validation<T>( T toTest );
public static T Validate<T>( this T leftside, Validation<T> validator )
{
    return validator(leftside) ? leftside : null;
}

Вслед за скитом:

Validation<Connection> v = ( Connection c ) => ( c != null && !( c.IsBusy || c. IsFull ) );
Connection bestConnection =
    server1.GetConnection().Validate( v ) ??
    server2.GetConnection().Validate( v ) ??
    server3.GetConnection().Validate( v ) ?? null;

Это как это будет работать в C #? Комментарии приветствуются. Спасибо.


В ответ на ShuggyCoUk :

Так это метод расширения в C # 3, тогда? Кроме того, результатом здесь является int, а не произвольное выражение. Полезно для перегрузки еще один метод сравнения. Предположим, мне нужно выражение для выбора лучшего соединения. В идеале я хочу кое-что упростить:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull) ? temp
    :   ( temp = server2.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
        :   ( temp = server3.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
            : null;

Хорошо, можно было бы получить метод

bool IsOk( Connection c )
{
    return ( c != null && !(c.IsBusy || c.IsFull) );
}

Что даст:

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) && IsOk( temp ) ? temp
    :   ( temp = server2.GetConnection() ) && IsOk( temp ) ? temp
        :   ( temp = server3.GetConnection() ) && IsOk( temp ) ? temp
            : null;

Но как здесь будет работать цепочка методов для сравнений? Я размышляю над чем-то похожим на:

Connection bestConnection =
    server1.GetConnection() unless !IsOk(value) otherwise
    server2.GetConnection() unless !IsOk(value) otherwise
    server3.GetConnection() unless !IsOk(value) otherwise null;

Я думаю, что пока есть обходы, если я хочу, чтобы результат условия был выражением или результатом метода, который был в исходном условии.

Я предполагаю, что объект, возвращаемый такими методами, будет дорогостоящим для производства или изменится при следующем вызове метода.

0 голосов
/ 05 августа 2009

Использование любых символов, как правило, ухудшает читабельность для среднего разработчика. Даже оператор ?? широко не используется. Я сам предпочитаю разрабатывать подробный код, но его легко прочитать через год.

Итак, кандидат на ваш счет:

выражение A, кроме случаев, когда логическое значение B; в этом случае выражение C.

будет

выражение A, если не логическое B sothen выражение C.

Хотя многие, как я, все равно будут использовать:

if (B) {expression C;}
else {expression A;}

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

0 голосов
/ 05 августа 2009

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

(выражение A ) unless (логическое B ) <магический оператор "в таком случае"> (выражение C )

... было бы все, что нужно для этого.

Логическое выражение B будет иметь доступ к оценке выражения A через ключевое слово value. Выражение C может содержать в своем выражении ключевое слово unless, что позволяет выполнять простое линейное сцепление.

Кандидаты в <магический оператор «в таком случае»>:

  • :
  • |
  • ?:
  • otherwise ключевое слово
...