2
\$\begingroup\$

Sorry, it used to be some retired garbage in Countable and uncountable sets. Now and here it is clean.

P.S. I would not like to say that this stuff is about to replace functional programming :) No, it is just about predicates becoming a full featured objects, so you can do math on them in the form of sets. We are always adding indirection over indirection in software development...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using static System.Console;
using static System.String;

class Program
{
    static void Main(string[] args)
    {
        var NullOrEmpty = new Set<string>(string.IsNullOrEmpty);
        var NullOrWhiteSpace = new Set<string>(string.IsNullOrWhiteSpace);
        var WhiteSpace = NullOrWhiteSpace - NullOrEmpty;

        WriteLine(WhiteSpace * " "); // True
        foreach (var s in !WhiteSpace * "Abba")
            WriteLine(s); // Abba

        var LowIncome = new Set<int>(i => i < 30000);
        var HighIncome = new Set<int>(i => i > 140000);
        var MiddleIncome = !LowIncome * !HighIncome;

        var salaries = new[] { 25000, 40000, 35000, 80000, 65000, 120000, 200000 };            
        WriteLine(Join(",", salaries - MiddleIncome)); // 25000, 200000
        WriteLine(Join(",", salaries * (LowIncome + HighIncome))); // 25000, 200000
        WriteLine(Join(",", salaries * !LowIncome)); // 40000,35000,80000,65000,120000,200000
    }
}

class Set<T>
{
    public Set(Predicate<T> predicate)
    {
        Predicate = predicate;
    }        

    public static Enumerable<T> operator *(Set<T> left, T right) =>
        left.Predicate(right) ? new Enumerable<T>(right) : Enumerable<T>.Empty;

    public static Enumerable<T> operator *(T left, Set<T> right) =>
        right.Predicate(left) ? new Enumerable<T>(left) : Enumerable<T>.Empty;

    public static Set<T> operator *(Set<T> left, Set<T> right) =>
        new Set<T>(i => left.Predicate(i) && right.Predicate(i));

    public static Enumerable<T> operator *(Set<T> left, IEnumerable<T> right) =>
        new Enumerable<T>(right.Where(i => left.Predicate(i)));

    public static Enumerable<T> operator *(IEnumerable<T> left, Set<T> right) =>
        new Enumerable<T>(left.Where(i => right.Predicate(i)));

    public static Set<T> operator +(Set<T> left, T right) =>
        new Set<T>(i => left.Predicate(i) || right.Equals(i));

    public static Set<T> operator +(T left, Set<T> right) =>
        new Set<T>(i => left.Equals(i) || right.Predicate(i));

    public static Set<T> operator +(Set<T> left, Set<T> right) =>
        new Set<T>(i => left.Predicate(i) || right.Predicate(i));

    public static Set<T> operator +(Set<T> left, IEnumerable<T> right) =>
        new Set<T>(i => left.Predicate(i) || right.Contains(i));

    public static Set<T> operator +(IEnumerable<T> left, Set<T> right) =>
        new Set<T>(i => left.Contains(i) || right.Predicate(i));

    public static Set<T> operator -(Set<T> left, T right) =>
        new Set<T>(i => left.Predicate(i) && !right.Equals(i));

    public static Set<T> operator -(T left, Set<T> right) =>
        new Set<T>(i => left.Equals(i) && !right.Predicate(i));

    public static Set<T> operator -(Set<T> left, Set<T> right) =>
        new Set<T>(i => left.Predicate(i) && !right.Predicate(i));

    public static Set<T> operator -(Set<T> left, IEnumerable<T> right) =>
        new Set<T>(i => left.Predicate(i) && !right.Contains(i));

    public static Enumerable<T> operator -(IEnumerable<T> left, Set<T> right) =>
        new Enumerable<T>(left.Where(i => !right.Predicate(i)));

    public static Set<T> operator !(Set<T> set) =>
        new Set<T>(i => !set.Predicate(i));

    Predicate<T> Predicate { get; }
}

class Enumerable<T> : IEnumerable<T>
{
    public static readonly Enumerable<T> Empty = new Enumerable<T>();
    public static implicit operator bool(Enumerable<T> intersection) => intersection.Any();

    public Enumerable(params T[] items)
    {
        Items = items;
    }

    public Enumerable(IEnumerable<T> items)
    {
        Items = items;
    }

    public IEnumerator<T> GetEnumerator() => Items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    IEnumerable<T> Items { get; }
}
\$\endgroup\$
6
  • 3
    \$\begingroup\$ I just have one doubt: you're trying to implement an "easy to use" predicate set for business logic rules. For this you feel you need some sugar to keep code short (sacrificing readability). However in my mind business rules are often serialized into text files because each customer will have slightly different requirements and no one will create and combine them by code (with the exception of some tests). What's this effort for? If language doesn't give you what you absolutely need then IMO you're just using wrong language. \$\endgroup\$ Commented Jun 15, 2016 at 5:32
  • \$\begingroup\$ It is a pretty common place that there is no better capture of the business logic than code - It just must be syntactically efficient, with a minimum of computer scaffolding like stupid type declarations where it is more than possible to deduce them. Sets are not a replacement to LINQ with extension methods, but Sets are approx 30-40% shorter. And as general as LINQ. It is good. P.S. I am taking Scala specialization at the moment - JVM has issues, but the language itself is really good... :) \$\endgroup\$ Commented Jun 15, 2016 at 7:04
  • 1
    \$\begingroup\$ I understand your feeling, if you want to describe business logic then more language is strict and verbose and more it's a pain. Howerver each language has been designed for a purpose. I'd avoid to workaround language syntax and rules, shorter code will become just confusing, not better. Do you need another syntax? Use another language (better if specifically designed to fit your requirements) \$\endgroup\$ Commented Jun 15, 2016 at 7:38
  • 1
    \$\begingroup\$ It appears to me that a set of extension methods would make your code much more readable: var predicate = myPredicate.And(otherPredicate) and collection.Where(predicate.Not()) are more verbose, but they're also much easier to understand (in the context of C#). \$\endgroup\$ Commented Jun 15, 2016 at 9:05
  • \$\begingroup\$ @PieterWitvoet I have changed + and - to & and | - it looks much better now. But the same time, good lambda language to help with composition might be useful, yes... \$\endgroup\$ Commented Jun 16, 2016 at 0:54

1 Answer 1

5
\$\begingroup\$

Naming:

Local variables should be lowerCamelCase. NullOrEmpty -> nullOrEmpty.

Argument validation:

public Enumerable(params T[] items)
{
    Items = items;
}

params arrays can be null. If that happens, your methods on the class will all throw NullReferenceExceptions. You should validate that the items array is not null in the constructor and throw an ArgumentNullException there instead.


Minor style points aside... This code is too clever to be used. E.g.

WriteLine(WhiteSpace * " "); // True

What the hell?! I multiply something called WhiteSpace by a space character and it returns true?! Don't you see how astonishing that is? How do I multiply a Set by a character? I have to dive into your code to realise that you've hijacked the thing that I've known for at least 20 years: * means multiply. Adding an implicit conversion to bool on your Enumerable<T> class is just icing on the "what on earth is going on" cake. Even with access to your source code (which I read before the demo code) it still took me probably 60 seconds to figure out what was going on.

The TL;DR; Optimise your code for other people reading it and don't try to add new meaning to a fundamental symbol that people already know.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.