Skip to content

Commit

Permalink
Docs update and editorconfig/cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
bartsokol committed Sep 15, 2017
1 parent e50d83a commit df64fb7
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 38 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = crlf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
2 changes: 1 addition & 1 deletion Monacs.Core/ErrorDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static class Errors
{
public static ErrorDetails Trace(string message = null, string key = null, Exception exception = null) =>
new ErrorDetails(ErrorLevel.Trace, message.ToOption(), key.ToOption(), exception.ToOption());

public static ErrorDetails Debug(string message = null, string key = null, Exception exception = null) =>
new ErrorDetails(ErrorLevel.Debug, message.ToOption(), key.ToOption(), exception.ToOption());

Expand Down
6 changes: 3 additions & 3 deletions Monacs.Core/Option.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static Option<string> OfString(string value) =>

public static T2 Match<T1, T2>(this Option<T1> option, Func<T1, T2> some, Func<T2> none) =>
option.IsSome ? some(option.Value) : none();

public static T2 MatchTo<T1, T2>(this Option<T1> option, T2 some, T2 none) =>
option.IsSome ? some : none;

Expand All @@ -91,7 +91,7 @@ public static Option<T2> Map<T1, T2>(this Option<T1> option, Func<T1, T2> mapper

public static T2 GetOrDefault<T1, T2>(this Option<T1> option, Func<T1, T2> getter, T2 whenNone = default(T2)) =>
option.IsSome ? getter(option.Value) : whenNone;

/* Side Effects */

public static Option<T> Do<T>(this Option<T> option, Action<T> action)
Expand All @@ -112,7 +112,7 @@ public static Option<T> DoWhenNone<T>(this Option<T> option, Action action)

public static IEnumerable<T> Choose<T>(this IEnumerable<Option<T>> items) =>
items.Where(i => i.IsSome).Select(i => i.Value);

public static Option<IEnumerable<T>> Sequence<T>(this IEnumerable<Option<T>> items) =>
items.Any(i => i.IsNone)
? None<IEnumerable<T>>()
Expand Down
6 changes: 3 additions & 3 deletions Monacs.Core/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static Result<string> OfString(string value, ErrorDetails error) =>

public static T2 Match<T1, T2>(this Result<T1> result, Func<T1, T2> ok, Func<ErrorDetails, T2> error) =>
result.IsOk ? ok(result.Value) : error(result.Error);

public static T2 MatchTo<T1, T2>(this Result<T1> result, T2 ok, T2 error) =>
result.IsOk ? ok : error;

Expand All @@ -102,7 +102,7 @@ public static Result<T2> Map<T1, T2>(this Result<T1> result, Func<T1, T2> mapper

public static T2 GetOrDefault<T1, T2>(this Result<T1> result, Func<T1, T2> getter, T2 whenError = default(T2)) =>
result.IsOk ? getter(result.Value) : whenError;

/* Side Effects */

public static Result<T> Do<T>(this Result<T> result, Action<T> action)
Expand All @@ -123,7 +123,7 @@ public static Result<T> DoWhenError<T>(this Result<T> result, Action<ErrorDetail

public static IEnumerable<T> Choose<T>(this IEnumerable<Result<T>> items) =>
items.Where(i => i.IsOk).Select(i => i.Value);

public static Result<IEnumerable<T>> Sequence<T>(this IEnumerable<Result<T>> items) =>
items.Any(i => i.IsError)
? Error<IEnumerable<T>>(items.First(i => i.IsError).Error)
Expand Down
6 changes: 3 additions & 3 deletions Monacs.UnitTests/ErrorDetailsTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module Constructors =
details.Message |> should equal (Option.Some(message))
details.Key |> should equal (Option.Some(key))
details.Exception |> should equal (Option.Some(ex))

[<Fact>]
let ``Info sets ErrorLevel.Info and error details`` () =
let message = "Message"
Expand All @@ -40,7 +40,7 @@ module Constructors =
details.Message |> should equal (Option.Some(message))
details.Key |> should equal (Option.Some(key))
details.Exception |> should equal (Option.Some(ex))

[<Fact>]
let ``Warn sets ErrorLevel.Warn and error details`` () =
let message = "Message"
Expand Down Expand Up @@ -72,4 +72,4 @@ module Constructors =
details.Level |> should equal ErrorLevel.Fatal
details.Message |> should equal (Option.Some(message))
details.Key |> should equal (Option.Some(key))
details.Exception |> should equal (Option.Some(ex))
details.Exception |> should equal (Option.Some(ex))
24 changes: 12 additions & 12 deletions Monacs.UnitTests/OptionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ module Converters =
let ``OfObject<T> returns Some<T> when value is not null`` () =
let object = obj()
Option.OfObject(object) |> should equal (Option.Some(object))

[<Fact>]
let ``OfNullable<T> returns None<T> when value is null`` () =
let empty = new Nullable<int>()
Expand All @@ -49,11 +49,11 @@ module Converters =
let ``OfNullable<T> returns Some<T> when value is not null`` () =
let value = Nullable(42)
Option.OfNullable(value) |> should equal (Option.Some(value.Value))

[<Fact>]
let ``OfString<T> returns None<T> when value is null`` () =
Option.OfString(null) |> should equal (Option.None<string>())

[<Fact>]
let ``OfString<T> returns None<T> when value is empty`` () =
Option.OfString("") |> should equal (Option.None<string>())
Expand All @@ -70,19 +70,19 @@ module Match =
let value = Option.None<int>()
let expected = "test"
Option.Match(value, some = (fun _ -> ""), none = (fun () -> expected)) |> should equal expected

[<Fact>]
let ``Match<T1, T2> returns result of some when value is Some<T1>`` () =
let value = Option.Some(42)
let expected = "test"
Option.Match(value, some = (fun _ -> expected), none = (fun () -> "")) |> should equal expected

[<Fact>]
let ``MatchTo<T1, T2> returns result of none when value is None<T1>`` () =
let value = Option.None<int>()
let expected = "test"
Option.MatchTo(value, some = "", none = expected) |> should equal expected

[<Fact>]
let ``MatchTo<T1, T2> returns result of some when value is Some<T1>`` () =
let value = Option.Some(42)
Expand All @@ -102,7 +102,7 @@ module Bind =
let value = Option.None<int>()
let expected = Option.None<string>()
Option.Bind(value, (fun x -> Option.Some(x.ToString()))) |> should equal expected

module Map =

[<Fact>]
Expand All @@ -118,7 +118,7 @@ module Map =
Option.Map(value, (fun x -> x.ToString())) |> should equal expected

module GetOrDefault =

[<Fact>]
let ``GetOrDefault<T> returns encapsulated value when value is Some<T>`` () =
let value = Option.Some("test")
Expand All @@ -136,7 +136,7 @@ module GetOrDefault =
let value = Option.None<string>()
let expected = "test"
Option.GetOrDefault(value, whenNone = expected) |> should equal expected

[<Fact>]
let ``GetOrDefault<T1, T2> returns getter result when value is Some<T1>`` () =
let value = Option.Some((1, "test"))
Expand Down Expand Up @@ -172,7 +172,7 @@ module ``Side effects`` =
let mutable result = expected
Option.Do(value, (fun x -> result <- "fail")) |> should equal value
result |> should equal expected

[<Fact>]
let ``DoWhenNone<T> returns value and doesn't execute action when value is Some<T>`` () =
let value = Option.Some(42)
Expand All @@ -196,15 +196,15 @@ module Collections =
let values = seq { yield Option.Some(42); yield Option.None(); yield Option.Some(123) }
let expected = [| 42; 123 |]
Option.Choose(values) |> Seq.toArray |> should equal expected

[<Fact>]
let ``Sequence<T> returns collection of values wrapped into Option when all items are not None<T>`` () =
let values = seq { yield Option.Some(42); yield Option.Some(123) }
let expected = [| 42; 123 |]
let result = Option.Sequence(values)
result.IsSome |> should equal true
result.Value |> Seq.toArray |> should equal expected

[<Fact>]
let ``Sequence<T> returns None<IEnumerable<T>> when any item is None<T>`` () =
let values = seq { yield Option.Some(42); yield Option.Some(123); yield Option.None() }
Expand Down
28 changes: 14 additions & 14 deletions Monacs.UnitTests/ResultTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ module ``Constructors and equality`` =
let ``Ok<T> doesn't equal Ok<T> when the Value is not equal`` () =
Result.Ok(42) = Result.Ok(13) |> should equal false
Result.Ok(42) <> Result.Ok(13) |> should equal true

[<Fact>]
let ``Error<T> equals Error<T> when the Error is equal`` () =
let error = Errors.Error()
Result.Error<string>(error) = Result.Error<string>(error) |> should equal true
Result.Error<string>(error) <> Result.Error<string>(error) |> should equal false

[<Fact>]
let ``Error<T> doesn't equal Error<T> when the Error is not equal`` () =
let error1 = Errors.Error("test1")
Expand All @@ -50,7 +50,7 @@ module Converters =
let ``OfObject<T> returns Ok<T> when value is not null`` () =
let object = obj()
Result.OfObject(object, Errors.Error()) |> should equal (Result.Ok(object))

[<Fact>]
let ``OfNullable<T> returns Error<T> when value is null`` () =
let empty = new Nullable<int>()
Expand All @@ -61,12 +61,12 @@ module Converters =
let ``OfNullable<T> returns Ok<T> when value is not null`` () =
let value = Nullable(42)
Result.OfNullable(value, Errors.Error()) |> should equal (Result.Ok(value.Value))

[<Fact>]
let ``OfString<T> returns Error<T> when value is null`` () =
let error = Errors.Error()
Result.OfString(null, error) |> should equal (Result.Error<string>(error))

[<Fact>]
let ``OfString<T> returns Error<T> when value is empty`` () =
let error = Errors.Error()
Expand All @@ -85,19 +85,19 @@ module Match =
let error = Errors.Error(expected)
let value = Result.Error<int>(error)
Result.Match(value, ok = (fun _ -> ""), error = (fun e -> e.Message.Value)) |> should equal expected

[<Fact>]
let ``Match<T1, T2> returns result of ok when value is Ok<T1>`` () =
let value = Result.Ok(42)
let expected = "test"
Result.Match(value, ok = (fun _ -> expected), error = (fun _ -> "")) |> should equal expected

[<Fact>]
let ``MatchTo<T1, T2> returns result of error when value is Error<T1>`` () =
let expected = "test"
let value = Result.Error<int>(Errors.Error())
Result.MatchTo(value, ok = "", error = expected) |> should equal expected

[<Fact>]
let ``MatchTo<T1, T2> returns result of ok when value is Ok<T1>`` () =
let value = Result.Ok(42)
Expand All @@ -118,7 +118,7 @@ module Bind =
let value = Result.Error<int>(error)
let expected = Result.Error<string>(error)
Result.Bind(value, (fun x -> Result.Ok(x.ToString()))) |> should equal expected

module Map =

[<Fact>]
Expand All @@ -135,7 +135,7 @@ module Map =
Result.Map(value, (fun x -> x.ToString())) |> should equal expected

module GetOrDefault =

[<Fact>]
let ``GetOrDefault<T> returns encapsulated value when value is Ok<T>`` () =
let value = Result.Ok("test")
Expand All @@ -153,7 +153,7 @@ module GetOrDefault =
let value = Result.Error<string>(Errors.Error())
let expected = "test"
Result.GetOrDefault(value, whenError = expected) |> should equal expected

[<Fact>]
let ``GetOrDefault<T1, T2> returns getter result when value is Ok<T1>`` () =
let value = Result.Ok((1, "test"))
Expand Down Expand Up @@ -189,7 +189,7 @@ module ``Side effects`` =
let mutable result = expected
Result.Do(value, (fun x -> result <- "fail")) |> should equal value
result |> should equal expected

[<Fact>]
let ``DoWhenError<T> returns value and doesn't execute action when value is Ok<T>`` () =
let value = Result.Ok(42)
Expand All @@ -213,15 +213,15 @@ module Collections =
let values = seq { yield Result.Ok(42); yield Result.Error(Errors.Error()); yield Result.Ok(123) }
let expected = [| 42; 123 |]
Result.Choose(values) |> Seq.toArray |> should equal expected

[<Fact>]
let ``Sequence<T> returns collection of values wrapped into Result when all items are not Error<T>`` () =
let values = seq { yield Result.Ok(42); yield Result.Ok(123) }
let expected = [| 42; 123 |]
let result = Result.Sequence(values)
result.IsOk |> should equal true
result.Value |> Seq.toArray |> should equal expected

[<Fact>]
let ``Sequence<T> returns Error<IEnumerable<T>> with first error when any item is Error<T>`` () =
let error = Errors.Error("error!")
Expand Down
36 changes: 36 additions & 0 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Getting started

Monacs is a library that provides a set of types and functions that can be used to substantialy change the approach you use to write your C# code. And while it won't change object-oriented language into fully-featured functional language, it gives you oportunity to use some of the FP concepts in your C# code today.

To fully leverage the potential of this library, you'll need to get familiar with a few simple concepts. Once you get through them, everything about this library should be pretty obvious.

## Don't fear the monad
The M Word. It's been a topic of countless discussions, jokes and even flamewars. You can look at the monad from many perspectives, but the perspective this library encourages is pretty simple: Monad is a combination of a type (e.g. Option) and a collection of functions around this type (like Map and Bind).

Actually, as a .NET developer you've probably used at least a couple of monads. `IEnumerable<T>` with LINQ is actually a monad. TPL and async/await workflow is a monad as well. And if you had chance to use Reactive Extensions and `IObservable<T>` type, then yes, it's a proper monad too.

You may find quite a few similarities between LINQ and what you'll find in this library, although the naming is a bit different. For example, both LINQ and Rx use `Select()` name for the function that makes a projection of an encapsulated value to the value of (potentially) other type. In Monacs you'll find it under the name `Map()`, which is quite common across FP languages.

## Higher order functions
This is another complicated name for a pretty simple concept. Higher order function is just a function which takes another function as a parameter. And again, if you've used LINQ, TPL or Rx you're probably quite familiar with this idea and I think it doesn't require more explanation.

## Value types
As you may know, there are two kinds of types in C# - reference types, such as `System.Object` or any class you write, and value types, such as `System.Int32` or enums. One particular thing that is very often underestimated by developers is the ability to create your own value types - structs. Apart from memory management differences, the key distinction between struct and class is the default value - the problematic null in reference type is substituted by the default value you create when designing a struct. And while you don't want to go and replace all your classes with structs, there are certain places where it can make a huge difference to not have to deal with (implicit) null.

## The power of extension methods
One of the most important features of C# that allowed to build this library is extension methods. Having the possibility to extend any type with additional methods from virtually any place gives us the flexibility to build modular fluent APIs around simple types.

## Leveraging `using static` imports
There is one particular feature of C# language that can significantly reduce the amount of code you have to write when you use the same static class multiple times. Take a look at an example:

namespace Monacs.Samples
{
using System;
public class Sample1
{
TODO
}
}

TODO
3 changes: 3 additions & 0 deletions docs/Index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Monacs - documentation

- **Intro**
- [Getting Started](GettingStarted.md)
- **Types**
- [Option\<T>](Option.md)
- [Result\<T>](Result.md)
Loading

0 comments on commit df64fb7

Please sign in to comment.