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

前端 未结 7 1949
死守一世寂寞
死守一世寂寞 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:

    /// TODO
    public struct IO : IEquatable> {
        /// Create a new instance of the class.
        public IO(Func functor) : this() { _functor = functor; }
    
        /// Invokes the internal functor, returning the result.
        public TSource Invoke() => (_functor | Default)();
    
        /// Returns true exactly when the contained functor is not null.
        public bool HasValue => _functor != null;
    
        X> _functor { get; }
    
        static Func 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 ToIO( this Func source) {
            source.ContractedNotNull(nameof(source));
            return new IO(source);
        }
    
        public static IO Select(this IO @this,
            Func projector
        ) =>
            @this.HasValue && projector!=null
                 ? New(() => projector(@this.Invoke()))
                 : Null();
    
        public static IO SelectMany(this IO @this,
            Func> selector
        ) =>
            @this.HasValue && selector!=null
                 ? New(() => selector(@this.Invoke()).Invoke())
                 : Null();
    
        public static IO SelectMany(this IO @this,
            Func> selector,
            Func projector
        ) =>
            @this.HasValue && selector!=null && projector!=null
                 ? New(() => { var s = @this.Invoke(); return projector(s, selector(s).Invoke()); } )
                 : Null();
    
        public static IO New (Func functor) => new IO(functor);
    
        private static IO Null() => new IO(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 one = new IO( () => { isExecuted1 = true; return 1; });
        IO two = new IO( () => { isExecuted2 = true; return 2; });
        Func> addOne = x => { isExecuted3 = true; return (x + 1).ToIO(); };
        Func>> 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, IComparable {
        [CLSCompliant(false)]
        public static Unit _ { get { return _this; } } static Unit _this = new Unit();
    }
    
    public static IO ConsoleWrite(object arg) =>
        ReturnIOUnit(() => Write(arg));
    
    public static IO ConsoleWriteLine(string value) =>
        ReturnIOUnit(() => WriteLine(value));
    
    public static IO ConsoleReadKey() => new IO(() => 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);
    

提交回复
热议问题