What's the idiomatic way to link language-ext to a Blazor/Asp.Net project? #1459
-
Scenario:
Current (bad) solution:In my core project:I define my Traits and helper methods. (This is ugly because I need to define "where UI : FolderPathIO" which is not the same as the Newsletter project). public interface FolderPathIO
{
public Unit UpdateFolderPath(string path);
public string GetFolderPath();
}
public static class FolderPath<M, RT, UI>
where RT : Has<M, UI>
where M : Monad<M>
where UI : FolderPathIO
{
static K<M, UI> trait => Has<M, RT, UI>.ask;
public static K<M, Unit> Update(string value) =>
from t in trait
let _ = t.UpdateFolderPath(value)
select _;
public static K<M, string> Get() =>
from t in trait
let path = t.GetFolderPath()
select path;
}I define dependency injection for my UI (SelectDocRT might be an incorrect term, could someone suggest a rename?) public interface SelectDocumentIO : ProgressIO, FolderPathIO, IsRunningIO { }
public record SelectDocumentModel(string InputFolderPath, bool IsRunning, double Progress);
public record SelectDocRT(Action<SelectDocumentModel> set, Func<SelectDocumentModel> get)
: SelectDocumentIO
{
public string GetFolderPath()
{
return get().InputFolderPath;
}
public Unit UpdateFolderPath(string path)
{
var x = get() with { InputFolderPath = path };
set(x);
return Unit.Default;
}
public Unit UpdateIsRunning(bool value)
{
var x = get() with { IsRunning = value };
set(x);
return Unit.Default;
}
public Unit UpdateProgress(double value)
{
var x = get() with { Progress = value };
set(x);
return Unit.Default;
}
}I define my logic: public static class SelectDocumentLogic<M>
where M : Monad<M>, Fallible<M>
{
public static K<M, string> SelectInputFolder<RT>()
where RT : Has<M, FolderPickerIO>, Has<M, SelectDocumentIO>
{
return from folderPath in FolderPicker<M, RT>.SelectInputFolder()
from _ in FolderPath<M, RT, SelectDocumentIO>.Update(folderPath)
select folderPath;
}
// other functions here
}In my Blazor projectI define my runtime: public record BaseRuntimeCore<TRuntime, TStateRT, TStateIO>(IEnv Env, TStateRT stateRT)
: Has<Eff<TRuntime>, AIQueryIO>,
Has<Eff<TRuntime>, FolderPickerIO>
where TRuntime : BaseRuntimeCore<TRuntime, TStateRT, TStateIO>
{
public static K<Eff<TRuntime>, AIQueryIO> Ask =>
Prelude.liftEff<TRuntime, AIQueryIO>(rt => rt.Env.AIQueryParams);
static K<Eff<TRuntime>, FolderPickerIO> Has<Eff<TRuntime>, FolderPickerIO>.Ask =>
Prelude.liftEff<TRuntime, FolderPickerIO>(rt => rt.Env.FolderPickerIO);
// other dependencies here
}
public record SelectDocRuntime(IEnv Env, SelectDocRT stateRT)
: BaseRuntimeCore<SelectDocRuntime, SelectDocRT, SelectDocumentIO>(Env, stateRT),
Has<Eff<SelectDocRuntime>, SelectDocumentIO>
{
static K<Eff<SelectDocRuntime>, SelectDocumentIO> Has<
Eff<SelectDocRuntime>,
SelectDocumentIO
>.Ask =>
Prelude.liftEff<SelectDocRuntime, SelectDocumentIO>(rt => (SelectDocumentIO)rt.stateRT);
}For EACH PAGE, I must define a different runtime and use it. namespace MauiBlazor2.Components.Pages;
public partial class Home
{
[Inject]
IEnv Environment { get; set; }
SelectDocumentModel Model = new SelectDocumentModel("", false, 0);
bool IsRunButtonEnabled => !Model.IsRunning && !string.IsNullOrEmpty(Model.InputFolderPath);
private SelectDocRT GetRT() =>
new SelectDocRT(
n =>
{
MainThread.InvokeOnMainThreadAsync(() =>
{
this.Model = n;
StateHasChanged();
});
},
() => this.Model
);
private async Task ExecuteSelectDocumentLogic<T>(
Func<K<Eff<SelectDocRuntime>, T>> logicProvider
)
{
var rt = new SelectDocRuntime(this.Environment, GetRT());
var outcome = await (
from fork in logicProvider().ForkIO()
from result in fork.Await
select result
).RunAsync(rt);
}
private async Task BrowseInputFolder(MouseEventArgs e) =>
await ExecuteSelectDocumentLogic(
SelectDocumentLogic<Eff<SelectDocRuntime>>.SelectInputFolder<SelectDocRuntime>
);
private async Task RunAI(MouseEventArgs e) =>
await ExecuteSelectDocumentLogic(
SelectDocumentLogic<Eff<SelectDocRuntime>>.ProcessFiles<SelectDocRuntime>
);
}QuestionIs there a better idiomatic way to do this? There's a lot of duplication and boilerplate here. I expect the "Mutates" trait might help me, but I can't find any examples of its use. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
Maybe
Is there a reason for a third generic arg Overall, I'm struggling to understand what are you trying to do. I've never used blazor, but I assume runtime injection is similar to when working with ASP.NET: just inject your runtime to IoC and request to your framework's dependent class like Page (I assume it's done with |
Beta Was this translation helpful? Give feedback.
-
I haven't had a chance to dig in too deep to what you wrote, but this ^^^ sticks out. Really, you should only need to define one runtime per application. The runtime defines the interface to 'the world'. It's your way of specifying how to talk to the mutable messiness which is the real world and collecting all of the 'real world problems' in a single, easily audited, place (this can really help if you're building security constructs into the runtime too). You then constrain individual methods with the In terms of idiomatic ways of implementing Blazor apps. Right now, nothing is defined. Initially I tried to avoid being too opinionated about how to integrate FP into C# apps as many people pick different levels of FP commitment (some just use However, I keep getting similar questions to this one, so I'm currently in the process of setting out some 'idiomatic' approaches. That's also part of the reason I'm doing a big refactor of the streaming features: I want to have a good story around events and event-streams and how they integrate with a real-world app (beyond Rx basically). Once it's done, my plan is to continue the Higher Kinds blog series expanding into how to build complete applications. I don't currently have a timeline for this right now, as I have a huge amount going on. Going back to the question of what if anything is idiomatic, well, nothing right now. Because the idioms haven't been defined. But the way I always like to think about anything that has a web request/response process is that a request is like an application that starts up, runs, returns a result and shuts down. So, the Newsletter sample being an an application that starts up, runs, returns a result and shuts down is the model for a single web-request. Essentially, we want to build a single pure effect that we can I realise this advice is quite generic. I'll expand upon it once I have some real world examples to show. |
Beta Was this translation helpful? Give feedback.
-
|
So, I've just added a I basically took the default Rider sample-app (which I think you have in your example too) and then did the following: Broke the
|
Beta Was this translation helpful? Give feedback.

So, I've just added a
BlazorAppsample to the repo. It's some early ideas that might work, or might collapse under a larger project, I just don't have enough Blazor experience to know.I basically took the default Rider sample-app (which I think you have in your example too) and then did the following:
Broke the
WeatherForecastdata structure out into aDatafolder:TemperatureCandTemperatureFand replaced it with the LanguageExt 'Units of Measure'Temperature.