I came across this dead end while trying to replace exceptions with either monad in c#. Which leads me to think maybe it is not only language specific problem and more techniqu
I haven't got a compiler handy, but you may want to check my language-ext project. It's a functional base class library for C#.
For your needs it has:
Seq
which is a cons like lazy enumerable which will only evaluate onceTry
which is a delegate based monad which allows you to capture exceptions from third party codeOption
, Either
, etc.OptionAsync
, TryOption
, TryAsync
, TryOptionAsync
ToOption()
, ToEither()
, etc.To apply a function (LINQ select,map...) on the method's lazy list argument and will take each element of the list (lazily) and will do computation that might fail (throwing an exception or returning Error/Either). The list to be consumed only "inside" the 3rd party function, I don't want to have to iterate over each element more then once.
This is a little unclear of the actual goal. In language-ext you could do this:
using LanguageExt;
using static LanguageExt.Prelude;
// Dummy lazy enumerable
IEnumerable Values()
{
for(int i = 0; i < 100; i++)
{
yield return UnreliableExternalFunc(i);
}
}
// Convert to a lazy sequence
Seq seq = Seq(Values());
// Invoke external function that takes an IEnumerable
ExternalFunction(seq);
// Calling it again won't evaluate it twice
ExternalFunction(seq);
But if the Values()
function threw an exception then it would end its yielding and would return. So you'd ideally have this:
// Dummy lazy enumerable
IEnumerable> Values()
{
for(int i = 0; i < 100; i++)
{
yield return Try(() => UnreliableExternalFunc(i));
}
}
Try
is the constructor function for the Try
monad. So your result would be a sequence of Try
thunks. If you don't care about the exception you could convert it to an Option
// Dummy lazy enumerable
IEnumerable
The you could access all successes via:
var validValues = Values().Somes();
Or you could instead use Either
:
// Dummy lazy enumerable
IEnumerable> Values()
{
for(int i = 0; i < 100; i++)
{
yield return Try(() => UnreliableExternalFunc(i)).ToEither();
}
}
Then you can get the valid results thus:
var seq = Seq(Values());
var validValues = seq.Rights();
And the errors:
var errors = seq.Lefts();
I converted it to a Seq
so it doesn't evaluate twice.
One way or another, if you want to catch an exception that happens during the lazy evaluation of the enumerable, then you will need to wrap each value. If the exception can occur from the usage of the lazy value, but within a function then your only hope is to surround it with a Try
:
// Convert to a lazy sequence
Seq seq = Seq(Values()); // Values is back to returning IEnumerable
// Invoke external function that takes an IEnumerable
var res = Try(() => ExternalFunction(seq)).IfFail(Seq.Empty);
// Calling it again won't evaluate it twice
ExternalFunction(seq);
Intuitively I was trying to convert the list from the list of Eithers to Either of list, but this can be done only by consuming the list with functions. Like, aggregate or reduce (does Haskell's Sequence function act the same?).
You can do this in language-ext like so:
IEnumerable> listOfEithers = ...;
Either> eitherList = listOfEithers.Sequence();
Traverse
is also supported:
Either> eitherList = listOfEithers.Traverse(x => map(x));
All combinations of monads support Sequence()
and Traverse
; so you could do it with a Seq
to get a Either
, which would guarantee that the lazy sequence isn't invoked multiple times. Or a Seq
to get a Try
, or any of the async variants for concurrent sequencing and traversal.
I'm not sure if any of this is covering what you're asking, the question is a bit broad. A more specific example would be useful.