diff --git a/docs/index.md b/docs/index.md index 7674b55..a61659b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -32,7 +32,7 @@ NuGet\Install-Package Rascal -IncludePrerelease # [Script environment](#tab/repl) -In environments such as [dotnet-script](https://github.com/dotnet-script/dotnet-script), [C# REPL](https://github.com/waf/CSharpRepl), or [RoslynPad](https://roslynpad.net), enter: +In environments such as [C# REPL](https://github.com/waf/CSharpRepl) or [RoslynPad](https://roslynpad.net), enter: ```cs #r "nuget: Rascal" @@ -44,15 +44,6 @@ If you wish to install a specific version of the package, specify the package ve #r "nuget: Rascal, 1.0.1-pre" ``` -For the special flavor of C# script mode used by the [MODiX REPL](https://github.com/discord-csharp/CSharpRepl): - -```cs -#nuget Rascal -``` - -> [!NOTE] -> The MODiX REPL currently doesn't support referencing specific package versions and therefore does not support pre-release versions of the package. - # [`PackageReference`](#tab/csproj) Add under an `ItemGroup` node in your project file: @@ -147,6 +138,12 @@ public sealed class UsersController(IUserService userService) : ControllerBase
+### More samples + +A plethora of additional code samples are available in the [samples](/samples/index.html) section of the documentation. + +
+ ### Explore the API Once you're ready to dive into the library, feel free to refer to the [API documentation](/api/index.html) for an in-depth look into each of the methods provided by the library. You can of course also explore the API through intellisense in your IDE of choice. diff --git a/docs/samples/index.md b/docs/samples/index.md new file mode 100644 index 0000000..9c30202 --- /dev/null +++ b/docs/samples/index.md @@ -0,0 +1,64 @@ +This article contains a variety of code samples demonstrating common usages for various parts of the Rascal library. All the samples source code can be found [here](https://github.com/thinker227/Rascal/tree/main/samples). + +
+ +## Creating results + +Results in Rascal can be created in a variety of ways, the two most common of which are through the [`Ok`](/api/Rascal.Prelude.html#Rascal_Prelude_Ok__1___0_) and [`Err`](/api/Rascal.Prelude.html#Rascal_Prelude_Err__1_Rascal_Error_) methods defined in the prelude, or through implicitly converting ok values or errors into results. + +[!code-csharp[](../../samples/Construction.csx#L7-L13)] + +
+ +## Mapping + +"Mapping" refers to taking a result containing some value some type (`T`) and *mapping* said value to a new value of some other type (`TNew`). The principal method of mapping is the aptly named [`Map`](/api/Rascal.Result-1.html#Rascal_Result_1_Map__1_System_Func__0___0__). + +[!code-csharp[](../../samples/Map.csx#L6-L14)] + +
+ +Another operation, commonly referred to as "bind" or "chaining", exists, which looks quite similar to mapping, the only difference being that the lambda you supply to the method returns a *new* result rather than a plain value. The principal method of chaining is [`Then`](/api/Rascal.Result-1.html#Rascal_Result_1_Then__1_System_Func__0_Rascal_Result___0___), which can be read as "a, then b, then c". + +[!code-csharp[](../../samples/Then.csx#L6-L19)] + +
+ +[`Map`](/api/Rascal.Result-1.html#Rascal_Result_1_Map__1_System_Func__0___0__) and [`Then`](/api/Rascal.Result-1.html#Rascal_Result_1_Then__1_System_Func__0_Rascal_Result___0___) together make up the core of the [`Result`](/api/Rascal.Result-1.html) type, allowing for chaining multiple operations on a single result. In functional terms, these are what makes [`Result`](/api/Rascal.Result-1.html) a functor and monad respectively (although not an applicative). + +
+ +> [!TIP] +> The aliases [`Select`](/api/Rascal.Result-1.html#Rascal_Result_1_Select__1_System_Func__0___0__) and [`SelectMany`](/api/Rascal.Result-1.html#Rascal_Result_1_SelectMany__1_System_Func__0_Rascal_Result___0___) are available for [`Map`](/api/Rascal.Result-1.html#Rascal_Result_1_Map__1_System_Func__0___0__) and [`Then`](/api/Rascal.Result-1.html#Rascal_Result_1_Then__1_System_Func__0_Rascal_Result___0___) respectively. These exist to supply support for *query expressions* as an alternative to method chaining. Query syntax can in specific situations be more readable than the method chaining alternative, although in *most* scenarios, method chaning is better. [`Select`](/api/Rascal.Result-1.html#Rascal_Result_1_Select__1_System_Func__0___0__) and [`SelectMany`](/api/Rascal.Result-1.html#Rascal_Result_1_SelectMany__1_System_Func__0_Rascal_Result___0___) should ***not*** be used outside query syntax. + +
+ +### Combine + +[`Combine`](/api/Rascal.Result-1.html#Rascal_Result_1_Combine__1_Rascal_Result___0__) is an addition to [`Map`](/api/Rascal.Result-1.html#Rascal_Result_1_Map__1_System_Func__0___0__) and [`Then`](/api/Rascal.Result-1.html#Rascal_Result_1_Then__1_System_Func__0_Rascal_Result___0___) which streamlines the specific case where you have two results and want to *combine* them into a single result only if both results are ok. + +[!code-csharp[](../../samples/Combine.csx#L6-L15)] + +
+ +## Validation + +Rascal supports a simple way of validating the value of a result, returning an error in case the validation fails. + +[!code-csharp[](../../samples/Validation.csx#L8-L24)] + +
+ +## Exception handling + +One of the major kinks of adapting C# into a more functional style (such as using results) is the already existing standard of using exceptions for error-handling. Exceptions have *many* flaws, and result types explicitly exist to provide a better alternative to exceptions, but Rascal nontheless provides a way to interoperate with traditional exception-based error handling. + +The [`Try`](/api/Rascal.Prelude.html#Rascal_Prelude_Try__1_System_Func___0__) method in the prelude is the premiere exception-handling method, which runs another function inside a `try`-`catch` block, and returns an [`ExceptionError`](/api/Rascal.Errors.ExceptionError.html) in case an exception is thrown. + +[!code-csharp[](../../samples/Try.csx#L6-L11)] + +
+ +`Try` variants also exist for [`Map`](/api/Rascal.Result-1.html#Rascal_Result_1_Map__1_System_Func__0___0__) and [`Then`](/api/Rascal.Result-1.html#Rascal_Result_1_Then__1_System_Func__0_Rascal_Result___0___), namely [`TryMap`](/api/Rascal.Result-1.html#Rascal_Result_1_TryMap__1_System_Func__0___0__) and [`ThenTry`](/api/Rascal.Result-1.html#Rascal_Result_1_ThenTry__1_System_Func__0_Rascal_Result___0___). + +[!code-csharp[](../../samples/TryMap.csx#L6-L11)] diff --git a/docs/samples/toc.yml b/docs/samples/toc.yml new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs/samples/toc.yml @@ -0,0 +1 @@ +[] diff --git a/docs/toc.yml b/docs/toc.yml index 7385472..c378383 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -1,3 +1,6 @@ +- name: Samples + href: samples/ + homepage: samples/index.md - name: API href: api/ homepage: api/index.md diff --git a/samples/.vscode/launch.json b/samples/.vscode/launch.json new file mode 100644 index 0000000..305245b --- /dev/null +++ b/samples/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Script Debug", + "type": "coreclr", + "request": "launch", + "program": "${env:HOME}/.dotnet/tools/dotnet-script", + "args": ["${file}"], + "windows": { + "program": "${env:USERPROFILE}/.dotnet/tools/dotnet-script.exe", + }, + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + } + ] +} \ No newline at end of file diff --git a/samples/Combine.csx b/samples/Combine.csx new file mode 100644 index 0000000..3dbd10e --- /dev/null +++ b/samples/Combine.csx @@ -0,0 +1,19 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using static Rascal.Prelude; + +// Read console input and assert that it isn't null. +var name = Console.ReadLine().NotNull(); + +// Read console input, assert that it isn't null, then try parse it into an int. +var age = Console.ReadLine().NotNull() + .Then(str => ParseR(str)); + +// Combine the name and age results together, then map them into a person. +var person = name.Combine(age) + .Map(v => new Person(v.first, v.second)); + +Console.WriteLine(person); + +record Person(string Name, int Age); diff --git a/samples/Construction.csx b/samples/Construction.csx new file mode 100644 index 0000000..bbad9fa --- /dev/null +++ b/samples/Construction.csx @@ -0,0 +1,15 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using Rascal.Errors; +using static Rascal.Prelude; + +// You can create a result either through explicit Ok/Error functions... +var explicitOk = Ok(new Person("Melody", 27)); +var explicitError = Err("Could not find person"); + +// ... or through implicit conversions... +Result implicitOk = new Person("Edwin", 32); +Result implicitError = new StringError("Failed to find person"); + +record Person(string Name, int Age); diff --git a/samples/Map.csx b/samples/Map.csx new file mode 100644 index 0000000..eaa15e7 --- /dev/null +++ b/samples/Map.csx @@ -0,0 +1,18 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using static Rascal.Prelude; + +var name = "Raymond"; + +// Read console input and try parse it into an int. +// If the input cannot be parsed, the result will be an error. +var age = ParseR(Console.ReadLine()!); + +// Map the age to a new person. +// If the age is an error, the person will also be an error. +var person = age.Map(x => new Person(name, x)); + +Console.WriteLine(person); + +record Person(string Name, int Age); diff --git a/samples/Then.csx b/samples/Then.csx new file mode 100644 index 0000000..e801721 --- /dev/null +++ b/samples/Then.csx @@ -0,0 +1,23 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using static Rascal.Prelude; + +// Read console input and assert that it isn't null. +// If the input is null, the value will be an error. +var name = Console.ReadLine().NotNull(); + +// Chain an operation on the name which will only execute if the name is ok. +var person = name.Then(n => +{ + // Read console input, assert that it isn't null, then try parse it into an int. + var age = Console.ReadLine().NotNull() + .Then(str => ParseR(str)); + + // Map the age into a new person. + return age.Map(a => new Person(n, a)); +}); + +Console.WriteLine(person); + +record Person(string Name, int Age); diff --git a/samples/Try.csx b/samples/Try.csx new file mode 100644 index 0000000..a67db88 --- /dev/null +++ b/samples/Try.csx @@ -0,0 +1,14 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using static Rascal.Prelude; + +// Try read console input and use the input to read the specified file. +// If an exception is thrown, the exception will be returned as an error. +var text = Try(() => +{ + var path = Console.ReadLine()!; + return File.ReadAllText(path); +}); + +Console.WriteLine(text); diff --git a/samples/TryMap.csx b/samples/TryMap.csx new file mode 100644 index 0000000..bcbb803 --- /dev/null +++ b/samples/TryMap.csx @@ -0,0 +1,13 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using Rascal; +using static Rascal.Prelude; + +// Read console input and assert that it isn't null. +var path = Console.ReadLine().NotNull(); + +// Try to map the input by reading a file specified by the input. +// If ReadAllText throws an exception, the exception will be returned as an error. +var text = path.TryMap(p => File.ReadAllText(p)); + +Console.WriteLine(text); diff --git a/samples/Validation.csx b/samples/Validation.csx new file mode 100644 index 0000000..a79d587 --- /dev/null +++ b/samples/Validation.csx @@ -0,0 +1,28 @@ +#r "../src/Rascal/bin/Release/net8.0/Rascal.dll" + +using System.Text.RegularExpressions; +using Rascal; +using Rascal.Errors; +using static Rascal.Prelude; + +// Read console input, assert that it isn't null, and validate that it matches the regex. +var name = Console.ReadLine().NotNull() + .Validate( + str => Regex.IsMatch(str, "[A-Z][a-z]*"), + _ => "Name can only contain characters a-z and has to start with a capital letter."); + +var person = name.Then(n => +{ + // Read console input, assert that it isn't null, try parse it into an int, then validate that it is greater than 0. + var age = Console.ReadLine().NotNull() + .Then(str => ParseR(str)) + .Validate( + x => x > 0, + _ => "Age has to be greater than 0."); + + return age.Map(a => new Person(n, a)); +}); + +Console.WriteLine(person); + +record Person(string Name, int Age); diff --git a/samples/omnisharp.json b/samples/omnisharp.json new file mode 100644 index 0000000..3789428 --- /dev/null +++ b/samples/omnisharp.json @@ -0,0 +1,6 @@ +{ + "script": { + "enableScriptNuGetReferences": true, + "defaultTargetFramework": "net8.0" + } +}