From 99893d3838e697026a0d450827ee9e52195233b4 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Fri, 12 Jan 2024 18:20:10 +0100 Subject: [PATCH] Update readme --- README.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de7bd5c..155d1ca 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,184 @@ Rascal is first and foremost an aggregate of the result types I personally find
# Installation + +
+.NET CLI + +Run in a terminal in the root of your project: + ```ps1 -# .NET CLI dotnet add package Rascal --prerelease ``` + +
+ +
+Package manager console + +Run from the Visual Studio Package Manager console: + +```ps1 +NuGet\Install-Package Rascal -IncludePrerelease +``` + +
+ +
+Script environment + +In environments such as [C# REPL](https://github.com/waf/CSharpRepl) or [RoslynPad](https://roslynpad.net), enter: + +```cs +#r "nuget: Rascal" +``` + +If you wish to install a specific version of the package, specify the package version: + +```cs +#r "nuget: Rascal, 1.0.1-pre" +``` + +
+ +
+PackageReference + +Add under an `ItemGroup` node in your project file: + +```xml + +``` + +Obviously, replace `1.0.1-pre` with the actual package version you want. + +
+ +
+ +# Samples + +## Creating results + + + +Results in Rascal can be created in a variety of ways, the two most common of which are through the `Ok` and `Err` methods defined in the prelude, or through implicitly converting ok values or errors into results. + +```cs +// 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"); +``` + +## 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`. + +```cs +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)); +``` + +
+ +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`, which can be read as "a, then b, then c". + +```cs +// 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)); +}); +``` + +
+ +`Map` and `Then` together make up the core of the `Result` type, allowing for chaining multiple operations on a single result. In functional terms, these are what makes `Result` a functor and monad respectively (although not an applicative). + +### Combine + +`Combine` is an addition to `Map` and `Then` 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. + +```cs +// 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)); +``` + +## Validation + +Rascal supports a simple way of validating the value of a result, returning an error in case the validation fails. + +```cs +// 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)); +}); +``` + +## 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` method in the prelude is the premiere exception-handling method, which runs another function inside a `try`-`catch` block, and returns an `ExceptionError` in case an exception is thrown. + +```cs +// 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); +}); +``` + +`Try` variants also exist for `Map` and `Then`, namely `TryMap` and `ThenTry`. + +```cs +// 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)); +```