using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Sony.Filtr.Functional { public static class FunctionalProgramming { public static Func Substract(double toSubstract) => x => x - toSubstract; public static Func Multiply(double multiplier) => x => x * multiplier; public static Func Divide(double divider) => x => x / divider; public static double Percentage(this double @this, double percentage) => @this * percentage / 100; public static R FirstOrDefault(this T @this, Func predicate, R @default, params Func[] prompts) { foreach (var prompt in prompts) { R r = prompt(@this); if (predicate(r)) { return r; } } return @default; } public static async Task> SafeExecute(this Func> func) { try { return await func(); } catch (Exception ex) { return Result.FromException(ex); } } public static Func Partial(this Func func, T1 arg) { return () => func(arg); } public static Func PartialLast(this Func func, T2 lastArg) { return t1 => func(t1, lastArg); } public static Func PartialLast(this Func func, T3 lastArg) { return (t1, t2) => func(t1, t2, lastArg); } public static Func PartialLast(this Func func, T4 lastArg) { return (t1, t2, t3) => func(t1, t2, t3, lastArg); } public static Func PartialLast(this Func func, T5 lastArg) { return (t1, t2, t3, t4) => func(t1, t2, t3, t4, lastArg); } public static Func Partial(this Func func, T1 arg) { return (t2) => func(arg, t2); } public static Func Partial(this Func func, T1 arg) { return (t2, t3) => func(arg, t2, t3); } public static Func Partial(this Func func, T1 arg) { return (t2, t3, t4) => func(arg, t2, t3, t4); } public static Func> Curry(this Func func) { return t1 => t2 => func(t1, t2); } public static Func>> Curry(this Func func) { return t1 => t2 => t3 => func(t1, t2, t3); } public static Func Memoize(this Func func) where T : IComparable { var cache = new Dictionary(); return arg => { if (cache.ContainsKey(arg)) { return cache[arg]; } return (cache[arg] = func(arg)); }; } public static Func, IEnumerable> MemoizeCollectionThreadSafe(this Func, IEnumerable> func, Func mapResultToArgument) where T : IComparable { var cache = new ConcurrentDictionary(); return arg => { var arg2 = arg.Except(cache.Keys); var response = func(arg2); foreach (var r in response) { cache.GetOrAdd(mapResultToArgument(r), _ => r); } return response; }; } public static Func, Task>> MemoizeCollectionThreadSafeAsync(this Func, Task>> func, Func mapResultToArgument) where T : IComparable { var cache = new ConcurrentDictionary>(); return async arg => { var arg2 = arg.Except(cache.Keys); if (arg2.Any()) { var response = await func(arg2); foreach (var r in response) { cache.GetOrAdd(mapResultToArgument(r), new Lazy(() => r)); } } return arg.Where(a => cache.ContainsKey(a)).Select(a => cache[a].Value).ToList(); }; } public static Func LazyMemoizeThreadSafe(this Func f) { var cache = new ConcurrentDictionary>(); return a => cache.GetOrAdd(a, new Lazy(() => f(a), true)).Value; } public static Func LazyMemoizeThreadSafeFirstOrDefault(this Func f, TResult defaultValue) { var cache = new ConcurrentDictionary>(); return a => { if (cache.ContainsKey(a)) { return defaultValue; } TResult toReturn = defaultValue; Func lazyFunc = () => { toReturn = f(a); return defaultValue; }; var m = cache.GetOrAdd(a, new Lazy(lazyFunc, true)).Value; return toReturn; }; } public static Func PartialFirst(Func func, T1 arg) { return func.Partial(arg); } public static IEnumerable Map(this IEnumerable ts, Func convert) { return ts.Select(t => convert(t)).ToArray(); } public static Func> BeforeStart(this Func> func, Action action) { return async x => { action(x); TOutput result = await func(x); return result; }; } public static Func> Duration(this Func> func, Action durationAction) { return async () => { var sw = new Stopwatch(); sw.Start(); R result = await func(); sw.Stop(); durationAction(result, sw.Elapsed); return result; }; } public static Func> Duration(this Func> func, Action durationAction) { return async x => { Stopwatch sw = new Stopwatch(); sw.Start(); TOutput result = await func(x); sw.Stop(); durationAction(x, result, sw.Elapsed); return result; }; } public static Func> Trace(this Func> func, Action traceAction, Func messageCreator) { Func createMessageSafe = input => { try { return messageCreator(input); } catch (Exception ex) { return $"Could not create trace message. Error: {ex.Message}"; } }; return async input => { string message = createMessageSafe(input); traceAction($"{message} on start {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ff")}"); try { TOutput result = await func(input); traceAction($"{message} on finish {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ff")}"); return result; } catch { traceAction($"{message} on ERROR {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ff")}"); throw; } }; } public static Func> Retry(this Func> func, int retries, TimeSpan? retryDelay = null) { if (retries < 1) { return func; } return async input => { Exception lastException = null; for (int i = 0; i <= retries; ++i) { try { return await func(input); } catch (AggregateException ex) { lastException = ex.InnerException; } catch (Exception ex) { lastException = ex; } if (retryDelay.HasValue) { await Task.Delay(retryDelay.Value); } } throw lastException; }; } public static Func> RetryWithCacellation(this Func> func, int retries, TimeSpan? retryDelay = null) { return async () => { Exception lastException = null; for (int i = 0; i <= retries; ++i) { var cts = new CancellationTokenSource(); try { return await func(cts.Token); } catch (AggregateException ex) { lastException = ex.InnerException; } catch (Exception ex) { lastException = ex; } cts.Cancel(); if (retryDelay.HasValue) { await Task.Delay(retryDelay.Value); } } throw lastException; }; } public static Func> Timeout(this Func> func, TimeSpan taskTimeout) { return async input => { var funcTask = func(input); var delayTask = Task.Delay(taskTimeout); var firstToFinishTask = await Task.WhenAny(funcTask, delayTask); if (firstToFinishTask == delayTask) { throw new TimeoutException($"Timed out: {taskTimeout.TotalSeconds} secs"); } return funcTask.Result; }; } public static Func> ToUnit(this Func action) { return async input => { await action(input); return Unit.Default; }; } public static Func<(T1, T2, T3, T4), R> Tuple(this Func func) { return tuple => func(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); } public static Func<(T1, T2, T3), R> Tuple(this Func func) { return tuple => func(tuple.Item1, tuple.Item2, tuple.Item3); } public static Func<(T1, T2), R> Tuple(this Func func) { return tuple => func(tuple.Item1, tuple.Item2); } public static Func<(T1, T2, T3, T4, T5), R> Tuple(this Func func) { return tuple => func(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5); } public static Func UnwrapTuple(this Func<(T1, T2), R> func) { return (t1, t2) => func((t1, t2)); } public static Func UnwrapTuple(this Func<(T1, T2, T3), R> func) { return (t1, t2, t3) => func((t1, t2, t3)); } public static Func> TupleResponse(this Func func) { return x => { R result = func(x); return new Tuple(x, result); }; } public static Func> ToTask(this Func func) { return x => { R result = func(x); return Task.FromResult(result); }; } public static Func> ToTask(this Action action) { return input => { action(input); return Task.FromResult(Unit.Default); }; } public static Task Catch(this Task task, Func onError) where TError : Exception { var tcs = new TaskCompletionSource(); task.ContinueWith(innerTask => { if (innerTask.IsFaulted && innerTask?.Exception?.InnerException is TError) tcs.SetResult(onError((TError)innerTask.Exception.InnerException)); else if (innerTask.IsCanceled) tcs.SetCanceled(); else if (innerTask.IsFaulted) tcs.SetException(innerTask?.Exception?.InnerException ?? throw new InvalidOperationException()); else tcs.SetResult(innerTask.Result); }); return tcs.Task; } public static Task Map(this Task input, Func map) => input.ContinueWith(t => map(t.Result)); public static Func> Map(this Func> input, Func inputTransform, Func outputTransform) { Func> f = async x => { TFuncIn funcInput = inputTransform(x); TFuncOut result = await input(funcInput); return outputTransform(funcInput, result, x); }; return f; } public static Func> Map(this Func> input, Func> inputTransform, Func> outputTransform) { Func> f = async x => { TFuncIn funcInput = await inputTransform(x); TFuncOut result = await input(funcInput); return await outputTransform(funcInput, result, x); }; return f; } //public static Task Apply(this Task task, Task> liftedFn) //{ // var tcs = new TaskCompletionSource(); // liftedFn.ContinueWith(innerLiftTask => // task.ContinueWith(innerTask => // tcs.SetResult(innerLiftTask.Result(innerTask.Result)) // )); // return tcs.Task; //} //public static Task Apply(this Task> liftedFn, Task task) => task.Apply(liftedFn); public static Task Select(this Task task, Func projection) { var r = new TaskCompletionSource(); task.ContinueWith(self => { if (self.IsFaulted) r.SetException(self.Exception.InnerExceptions); else if (self.IsCanceled) r.SetCanceled(); else r.SetResult(projection(self.Result)); }); return r.Task; } //public static Task> Apply(this Task> liftedFn, Task input) // => input.Apply(liftedFn.Map(FunctionalProgramming.Curry)); public static Func>>> SelectMany(this Func>>> func) { return async x => { Result> result = await func(x); return result.IsFailed ? new Result[] { Result.FromException(result.Exception) } : result.Value.Select(r => Result.Success(r)).ToArray(); }; } //public static Task SelectMany(this Task first, Func> next) //{ // var tcs = new TaskCompletionSource(); // first.ContinueWith(delegate // { // if (first.IsFaulted) tcs.TrySetException(first.Exception.InnerExceptions); // else if (first.IsCanceled) tcs.TrySetCanceled(); // else // { // try // { // var t = next(first.Result); // if (t == null) tcs.TrySetCanceled(); // else t.ContinueWith(delegate // { // if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions); // else if (t.IsCanceled) tcs.TrySetCanceled(); // else tcs.TrySetResult(t.Result); // }, TaskContinuationOptions.ExecuteSynchronously); // } // catch (Exception exc) { tcs.TrySetException(exc); } // } // }, TaskContinuationOptions.ExecuteSynchronously); // return tcs.Task; //} //public static Task SelectMany( // this Task input, Func> f, Func projection) //{ // return Bind(input, outer => // Bind(f(outer), inner => // Return(projection(outer, inner)))); //} public static Task Next(this Task task, Func> next) { if (task == null) throw new ArgumentNullException("task"); if (next == null) throw new ArgumentNullException("next"); var tcs = new TaskCompletionSource(); task.ContinueWith(delegate { if (task.IsFaulted) tcs.TrySetException(task.Exception.InnerExceptions); else if (task.IsCanceled) tcs.TrySetCanceled(); else { try { var t = next(task.Result); if (t == null) tcs.TrySetCanceled(); else t.ContinueWith(delegate { if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions); else if (t.IsCanceled) tcs.TrySetCanceled(); else tcs.TrySetResult(t.Result); }, TaskContinuationOptions.ExecuteSynchronously); } catch (Exception exc) { tcs.TrySetException(exc); } } }, TaskContinuationOptions.ExecuteSynchronously); return tcs.Task; } public static IEnumerable> ProcessAsComplete(this IEnumerable> inputTasks) { // Copy the input so we know it’ll be stable, and we don’t evaluate it twice var inputTaskList = inputTasks.ToList(); // Could use Enumerable.Range here, if we wanted… var completionSourceList = new List>(inputTaskList.Count); for (int i = 0; i < inputTaskList.Count; i++) { completionSourceList.Add(new TaskCompletionSource()); } // At any one time, this is "the index of the box we’ve just filled". // It would be nice to make it nextIndex and start with 0, but Interlocked.Increment // returns the incremented value… int prevIndex = -1; // We don’t have to create this outside the loop, but it makes it clearer // that the continuation is the same for all tasks. Action> continuation = completedTask => { int index = Interlocked.Increment(ref prevIndex); var source = completionSourceList[index]; switch (completedTask.Status) { case TaskStatus.Canceled: source.TrySetCanceled(); break; case TaskStatus.Faulted: source.TrySetException(completedTask.Exception.InnerExceptions); break; case TaskStatus.RanToCompletion: source.TrySetResult(completedTask.Result); break; default: throw new ArgumentException("Task was not completed"); } }; foreach (var inputTask in inputTaskList) { inputTask.ContinueWith(continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } return completionSourceList.Select(source => source.Task); } } public static class FuncUtils { public static Func Partial(Func func, T1 firstParam) { return t2 => func(firstParam, t2); } public static Func Partial(Func func, T1 firstParam) { return (t2, t3) => func(firstParam, t2, t3); } public static Func Partial(Func func, T1 firstParam) { return (t2, t3, t4) => func(firstParam, t2, t3, t4); } public static Func Partial(Func func, T1 firstParam) { return (t2, t3, t4, t5) => func(firstParam, t2, t3, t4, t5); } public static Func> ToFunc(Func> func) { return func; } } public static class ResultExtensions1 { public static Func>> TryCatch(this Func> func) { return async () => { try { return await func(); } catch (Exception ex) { return ex; } }; } public static Func> TryCatch(this Action action) { return () => { Stopwatch sw = new Stopwatch(); sw.Start(); try { action(); } catch (Exception ex) { sw.Stop(); return Result.FromException(ex, sw.Elapsed); } return Result.Success(Unit.Default); }; } public static Func>> TryCatch(this Func> func) { return async x => { Stopwatch sw = new Stopwatch(); sw.Start(); try { return await func(x); } catch (Exception ex) { sw.Stop(); return Result.FromException(ex, sw.Elapsed); } }; } public static Func> TryCatch(this Func func) { return x => { Stopwatch sw = new Stopwatch(); sw.Start(); try { return func(x); } catch (Exception ex) { sw.Stop(); return Result.FromException(ex, sw.Elapsed); } }; } public static Func, Task> AcceptResult(this Func> func) { return result => { return func(result.Value); }; } public static async Task> OnSuccess(this Task> resultTask, Func> func) { Result result = await resultTask.ConfigureAwait(false); if (result.IsFailed) { return Result.FromException(result.Exception); } return await func(result.Value).ConfigureAwait(false); } public static async Task> OnSuccess(this Task> resultTask, Func func) { Result result = await resultTask.ConfigureAwait(false); if (result.IsFailed) { return Result.FromException(result.Exception); } await func(result.Value).ConfigureAwait(false); return result; } public static async Task> OnSuccess(this Task> resultTask, Action action) { Result result = await resultTask.ConfigureAwait(false); if (result.IsFailed) { return Result.FromException(result.Exception); } action(result.Value); return result; } public static Func> OnFailure(this Func> resultFunc, Action> failureAction) { return () => { var result = resultFunc(); if (result.IsFailed) { failureAction(result); } return result; }; } public static async Task> OnFailure(this Task> resultTask, Func func) { Result result = await resultTask.ConfigureAwait(false); if (result.IsFailed) await func().ConfigureAwait(false); return result; } public static async Task> OnFailure(this Task> resultTask, Action action) { Result result = await resultTask.ConfigureAwait(false); if (result.IsFailed) { action(result.Exception); } return result; } public static Func>> OnFailure(this Func>> func, Action> failureAction) { return async x => { Result result = await func(x); if (result.IsFailed) { failureAction(x, result); } return result; }; } public static Func>> OnFailure(this Func>> func, Action> failureAction) { return async () => { Result result = await func(); if (result.IsFailed) { failureAction(result); } return result; }; } public static Func>> OnSuccess(this Func>> func, Action> successAction) { return async x => { Result result = await func(x); if (result.IsOk) { successAction(x, result); } return result; }; } public static Func>> BindAsync(this Func>> func, Func project) { return async x => { Result result = await func(x); return result.IsOk ? project(result.Value) : Result.FromException(result.Exception); }; } public static Func>> BindAsync(this Func>> func, Func project) { return async x => { Result result = await func(x); return result.IsOk ? project(x, result.Value) : Result.FromException(result.Exception); }; } public static Func>> TapAsync(this Func>> func, Action action) { return async x => { Result result = await func(x); if (result.IsOk) { action(result.Value); } return result; }; } public static Func>> TapAsync(this Func>> func, Action action) { return async x => { Result result = await func(x); if (result.IsOk) { action(x, result.Value); } return result; }; } public static Func> OnSuccessUnsafe(this Func> func, Action successAction) { return async x => { R result = await func(x); successAction(x, result); return result; }; } public static Func> OnFailureWithRethrow(this Func> func, Action> failureAction) { return async x => { Stopwatch sw = new Stopwatch(); sw.Start(); try { R result = await func(x); return result; } catch (Exception e) { sw.Stop(); failureAction(x, Result.FromException(e, sw.Elapsed)); throw; } }; } } }