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
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);