Skip to content

Commit

Permalink
Add support for IO.Action and IO.Actions in IO monad
Browse files Browse the repository at this point in the history
Implemented `IO` applicative methods to handle sequential actions (`Action`) and collections of actions (`Actions`). Updated console methods to use generic monadic operations, improving flexibility and alignment with the new `IO` monad changes.
  • Loading branch information
louthy committed Dec 25, 2024
1 parent a916fb9 commit 6dc7072
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static Eff<RT, RT> runtime<RT>() =>
[Pure, MethodImpl(Opt.Default)]
public static Eff<RT, (RT Runtime, EnvIO EnvIO)> getState<RT>() =>
new(LanguageExt.Eff<RT>.getState);

/// <summary>
/// Create a new cancellation context and run the provided Aff in that context
/// </summary>
Expand All @@ -55,14 +55,7 @@ public static Eff<RT, RT> runtime<RT>() =>
/// <typeparam name="A">Bound value type</typeparam>
/// <returns>An asynchronous effect that captures the operation running in context</returns>
public static Eff<RT, A> localCancel<RT, A>(Eff<RT, A> ma) =>
from s in getState<RT>()
from r in liftEff<RT, A>(
rt =>
{
using var lenvIO = s.EnvIO.LocalCancel;
return ma.Run(rt, lenvIO);
})
select r;
ma.LocalIO().As();

/// <summary>
/// Create a new local context for the environment by mapping the outer environment and then
Expand Down
42 changes: 42 additions & 0 deletions LanguageExt.Core/Effects/IO/DSL/IOAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Threading.Tasks;
using LanguageExt.Traits;

namespace LanguageExt.DSL;

record IOAction<A, B, C>(K<IO, A> Fa, K<IO, B> Fb, Func<B, IO<C>> Next) : InvokeSyncIO<C>
{
public override IO<D> Map<D>(Func<C, D> f) =>
new IOAction<A, B, D>(Fa, Fb, b => Next(b).Map(f));

public override IO<D> Bind<D>(Func<C, K<IO, D>> f) =>
new IOAction<A, B, D>(Fa, Fb, b => Next(b).Bind(f));

public override IO<C> Invoke(EnvIO envIO)
{
var taskA = Fa.As().RunAsync(envIO);
if (taskA.IsCompleted)
{
return Fb.Bind(Next).As();
}
else
{
return new IOActionAsync<A, B, C>(taskA, Fb, Next);
}
}
}

record IOActionAsync<A, B, C>(ValueTask<A> Fa, K<IO, B> Fb, Func<B, IO<C>> Next) : InvokeAsyncIO<C>
{
public override IO<D> Map<D>(Func<C, D> f) =>
new IOActionAsync<A, B, D>(Fa, Fb, b => Next(b).Map(f));

public override IO<D> Bind<D>(Func<C, K<IO, D>> f) =>
new IOActionAsync<A, B, D>(Fa, Fb, b => Next(b).Bind(f));

public override async ValueTask<IO<C>> Invoke(EnvIO envIO)
{
await Fa;
return Fb.Bind(Next).As();
}
}
80 changes: 80 additions & 0 deletions LanguageExt.Core/Effects/IO/DSL/IOActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using System;
using System.Threading.Tasks;
using LanguageExt.Common;
using LanguageExt.Traits;

namespace LanguageExt.DSL;

record IOActions<A, B>(Iterator<K<IO, A>> Fas, Func<A, IO<B>> Next) : InvokeSyncIO<B>
{
public override IO<C> Map<C>(Func<B, C> f) =>
new IOActions<A, C>(Fas, x => Next(x).Map(f));

public override IO<C> Bind<C>(Func<B, K<IO, C>> f) =>
new IOActions<A, C>(Fas, x => Next(x).Bind(f));

public override IO<B> Invoke(EnvIO envIO)
{
if (Fas.IsEmpty)
{
return IO.fail<B>(Error.New("Actions is empty"));
}
else
{
var head = Fas.Head;
var task = head.RunAsync(envIO);
if (task.IsCompleted)
{
return new IOActionsSync<A, B>(task.Result, Fas.Tail, Next);
}
else
{
return new IOActionsAsync<A, B>(task, Fas.Tail, Next);
}
}
}
}

record IOActionsSync<A, B>(A Value, Iterator<K<IO, A>> Fas, Func<A, IO<B>> Next) : InvokeSyncIO<B>
{
public override IO<C> Map<C>(Func<B, C> f) =>
new IOActionsSync<A, C>(Value, Fas, x => Next(x).Map(f));

public override IO<C> Bind<C>(Func<B, K<IO, C>> f) =>
new IOActionsSync<A, C>(Value, Fas, x => Next(x).Bind(f));

public override IO<B> Invoke(EnvIO envIO)
{
if (Fas.IsEmpty)
{
return Next(Value);
}
else
{
return new IOActions<A, B>(Fas, Next);
}
}
}

record IOActionsAsync<A, B>(ValueTask<A> Value, Iterator<K<IO, A>> Fas, Func<A, IO<B>> Next) : InvokeAsyncIO<B>
{
public override IO<C> Map<C>(Func<B, C> f) =>
new IOActionsAsync<A, C>(Value, Fas, x => Next(x).Map(f));

public override IO<C> Bind<C>(Func<B, K<IO, C>> f) =>
new IOActionsAsync<A, C>(Value, Fas, x => Next(x).Bind(f));

public override async ValueTask<IO<B>> Invoke(EnvIO envIO)
{
var value = await Value;

if (Fas.IsEmpty)
{
return Next(value);
}
else
{
return new IOActions<A, B>(Fas, Next);
}
}
}
7 changes: 7 additions & 0 deletions LanguageExt.Core/Effects/IO/IO.Monad.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using LanguageExt.Common;
using LanguageExt.DSL;
using LanguageExt.Traits;
Expand All @@ -14,6 +15,12 @@ public partial class IO :
static K<IO, B> Applicative<IO>.Apply<A, B>(K<IO, Func<A, B>> mf, K<IO, A> ma) =>
ma.As().ApplyBack(mf.As());

static K<IO, B> Applicative<IO>.Action<A, B>(K<IO, A> ma, K<IO, B> mb) =>
new IOAction<A, B, B>(ma, mb, pure);

static K<IO, A> Applicative<IO>.Actions<A>(IEnumerable<K<IO, A>> fas) =>
new IOActions<A, A>(fas.GetIterator(), pure);

static K<IO, B> Monad<IO>.Bind<A, B>(K<IO, A> ma, Func<A, K<IO, B>> f) =>
ma.As().Bind(f);

Expand Down
8 changes: 4 additions & 4 deletions LanguageExt.Sys/Sys/Console.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ from __ in Proxy.yield(ln)
public static K<M, int> read =>
from t in consoleIO
from k in t.Read()
from r in k.Match(Some: IO<int>.Pure,
None: Errors.EndOfStream)
from r in k.Match(Some: M.Pure,
None: M.Fail<int>(Errors.EndOfStream))
select r;

/// <summary>
Expand All @@ -68,8 +68,8 @@ from __ in Proxy.yield(ln)
public static K<M, string> readLine =>
from t in consoleIO
from k in t.ReadLine()
from r in k.Match(Some: IO<string>.Pure,
None: Errors.EndOfStream)
from r in k.Match(Some: M.Pure,
None: M.Fail<string>(Errors.EndOfStream))
select r;

/// <summary>
Expand Down

0 comments on commit 6dc7072

Please sign in to comment.