Is there idiomatic C# equivalent to C's comma operator?

前端 未结 7 1942
死守一世寂寞
死守一世寂寞 2020-12-14 07:18

I\'m using some functional stuff in C# and keep getting stuck on the fact that List.Add doesn\'t return the updated list.

In general, I\'d like to call

相关标签:
7条回答
  • 2020-12-14 07:54

    Another technique, straight from functional programming, is as follows. Define an IO struct like this:

    /// <summary>TODO</summary>
    public struct IO<TSource> : IEquatable<IO<TSource>> {
        /// <summary>Create a new instance of the class.</summary>
        public IO(Func<TSource> functor) : this() { _functor = functor; }
    
        /// <summary>Invokes the internal functor, returning the result.</summary>
        public TSource Invoke() => (_functor | Default)();
    
        /// <summary>Returns true exactly when the contained functor is not null.</summary>
        public bool HasValue => _functor != null;
    
        X<Func<TSource>> _functor { get; }
    
        static Func<TSource> Default => null;
    }
    

    and make it a LINQ-able monad with these extension methods:

    [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
    public static class IO {
        public static IO<TSource> ToIO<TSource>( this Func<TSource> source) {
            source.ContractedNotNull(nameof(source));
            return new IO<TSource>(source);
        }
    
        public static IO<TResult> Select<TSource,TResult>(this IO<TSource> @this,
            Func<TSource,TResult> projector
        ) =>
            @this.HasValue && projector!=null
                 ? New(() => projector(@this.Invoke()))
                 : Null<TResult>();
    
        public static IO<TResult> SelectMany<TSource,TResult>(this IO<TSource> @this,
            Func<TSource,IO<TResult>> selector
        ) =>
            @this.HasValue && selector!=null
                 ? New(() => selector(@this.Invoke()).Invoke())
                 : Null<TResult>();
    
        public static IO<TResult> SelectMany<TSource,T,TResult>(this IO<TSource> @this,
            Func<TSource, IO<T>> selector,
            Func<TSource,T,TResult> projector
        ) =>
            @this.HasValue && selector!=null && projector!=null
                 ? New(() => { var s = @this.Invoke(); return projector(s, selector(s).Invoke()); } )
                 : Null<TResult>();
    
        public static IO<TResult> New<TResult> (Func<TResult> functor) => new IO<TResult>(functor);
    
        private static IO<TResult> Null<TResult>() => new IO<TResult>(null);
    }
    

    and now you can use the LINQ comprehensive syntax thus:

    using Xunit;
    [Fact]
    public static void IOTest() {
        bool isExecuted1 = false;
        bool isExecuted2 = false;
        bool isExecuted3 = false;
        bool isExecuted4 = false;
        IO<int> one = new IO<int>( () => { isExecuted1 = true; return 1; });
        IO<int> two = new IO<int>( () => { isExecuted2 = true; return 2; });
        Func<int, IO<int>> addOne = x => { isExecuted3 = true; return (x + 1).ToIO(); };
        Func<int, Func<int, IO<int>>> add = x => y => { isExecuted4 = true; return (x + y).ToIO(); };
    
        var query1 = ( from x in one
                       from y in two
                       from z in addOne(y)
                       from _ in "abc".ToIO()
                       let addOne2 = add(x)
                       select addOne2(z)
                     );
        Assert.False(isExecuted1); // Laziness.
        Assert.False(isExecuted2); // Laziness.
        Assert.False(isExecuted3); // Laziness.
        Assert.False(isExecuted4); // Laziness.
        int lhs = 1 + 2 + 1;
        int rhs = query1.Invoke().Invoke();
        Assert.Equal(lhs, rhs); // Execution.
    
        Assert.True(isExecuted1);
        Assert.True(isExecuted2);
        Assert.True(isExecuted3);
        Assert.True(isExecuted4);
    }
    

    When one desires an IO monad that composes but returns only void, define this struct and dependent methods:

    public struct Unit : IEquatable<Unit>, IComparable<Unit> {
        [CLSCompliant(false)]
        public static Unit _ { get { return _this; } } static Unit _this = new Unit();
    }
    
    public static IO<Unit> ConsoleWrite(object arg) =>
        ReturnIOUnit(() => Write(arg));
    
    public static IO<Unit> ConsoleWriteLine(string value) =>
        ReturnIOUnit(() => WriteLine(value));
    
    public static IO<ConsoleKeyInfo> ConsoleReadKey() => new IO<ConsoleKeyInfo>(() => ReadKey());
    

    which readily allow the writing of code fragments like this:

    from pass  in Enumerable.Range(0, int.MaxValue)
    let counter = Readers.Counter(0)
    select ( from state in gcdStartStates
             where _predicate(pass, counter())
             select state )
    into enumerable
    where ( from _   in Gcd.Run(enumerable.ToList()).ToIO()
            from __  in ConsoleWrite(Prompt(mode))
            from c   in ConsoleReadKey()
            from ___ in ConsoleWriteLine()
            select c.KeyChar.ToUpper() == 'Q' 
          ).Invoke()
    select 0;
    

    where the old C comma operator is readily recognized for what it is: a monadic compose operation.

    The true merit of the comprehension syntax is apparent when one attempts to write that fragment in the flunt style:

    ( Enumerable.Range(0,int.MaxValue)
                .Select(pass => new {pass, counter = Readers.Counter(0)})
                .Select(_    => gcdStartStates.Where(state => _predicate(_.pass,_.counter()))
                                              .Select(state => state)
                       )
    ).Where(enumerable => 
       ( (Gcd.Run(enumerable.ToList()) ).ToIO()
            .SelectMany(_ => ConsoleWrite(Prompt(mode)),(_,__) => new {})
            .SelectMany(_ => ConsoleReadKey(),          (_, c) => new {c})
            .SelectMany(_ => ConsoleWriteLine(),        (_,__) => _.c.KeyChar.ToUpper() == 'Q')
        ).Invoke()
    ).Select(list => 0);
    
    0 讨论(0)
  • 2020-12-14 07:56

    You can do almost exactly the first example naturally using code blocks in C# 3.0.

    ((accum, data) => { accum.Add(data); return accum; })
    
    0 讨论(0)
  • 2020-12-14 07:59

    I know that this thread is very old, but I want to append the following information for future users:

    There isn't currently such an operator. During the C# 6 development cycle a semicolon operator was added, as:

    int square = (int x = int.Parse(Console.ReadLine()); Console.WriteLine(x - 2); x * x);
    

    which can be translated as follows:

    int square = compiler_generated_Function();
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private int compiler_generated_Function()
    {
        int x = int.Parse(Console.ReadLine());
    
        Console.WriteLine(x - 2);
    
        return x * x;
    }
    

    However, this feature was dropped before the final C# release.

    0 讨论(0)
  • 2020-12-14 08:00

    The extension method is arguably the best solution, but for completeness' sake, don't forget the obvious alternative: a wrapper class.

    public class FList<T> : List<T>
    {
        public new FList<T> Add(T item)
        {
            base.Add(item);
            return this;
        }
    
        public new FList<T> RemoveAt(int index)
        {
            base.RemoveAt(index);
            return this;
        }
    
        // etc...
    }
    
    {
         var list = new FList<string>();
         list.Add("foo").Add("remove me").Add("bar").RemoveAt(1);
    }
    
    0 讨论(0)
  • 2020-12-14 08:02

    This is what Concat http://msdn.microsoft.com/en-us/library/vstudio/bb302894%28v=vs.100%29.aspx is for. Just wrap a single item in an array. Functional code should not mutate the original data. If performance is a concern, and this isn't good enough, then you'll no longer be using the functional paradigm.

    ((accum, data) => accum.Concat(new[]{data}))
    
    0 讨论(0)
  • 2020-12-14 08:05

    I know this as Fluent.

    A Fluent example of a List.Add using Extension Methods

    static List<T> MyAdd<T>(this List<T> list, T element)
    {
        list.Add(element);
        return list;
    }
    
    0 讨论(0)
提交回复
热议问题