├── .gitignore
├── Boc.Domain
├── Boc.Domain.fsproj
└── Lib.fs
├── Examples
├── Chapter01
│ ├── Circle.cs
│ ├── DbLogger
│ │ ├── ConnectionHelper.cs
│ │ ├── DbLogger_V1.cs
│ │ ├── DbLogger_V2.cs
│ │ ├── LogMessage.cs
│ │ └── TimeSpanExt.cs
│ ├── Employee.cs
│ ├── Functions.cs
│ ├── HOFs.cs
│ └── Tenets.cs
├── Chapter02
│ ├── Boc
│ │ ├── BicExistsValidator.cs
│ │ ├── Commands.cs
│ │ ├── DateNotPastValidator.NotTestable.cs
│ │ ├── DateNotPastValidator.TestableWithInjectedValue.cs
│ │ ├── DateNotPastValidator.TestableWithInterface.cs
│ │ └── IValidator.cs
│ ├── ListFormatter
│ │ ├── ListFormatter_InfiniteSequence.cs
│ │ ├── ListFormatter_Instance.cs
│ │ ├── ListFormatter_ParNaive.cs
│ │ ├── ListFormatter_ParZip.cs
│ │ ├── ListFormatter_Static.cs
│ │ ├── ListFormatter_Zip.cs
│ │ ├── Numbered.cs
│ │ └── StringExt.cs
│ ├── MutatingArguments.cs
│ └── Zip.cs
├── Chapter03
│ ├── Age.cs
│ ├── ConfigurationExt.cs
│ ├── Earnings.cs
│ ├── FunctionComposition.cs
│ ├── Instrumentation.cs
│ ├── MethodChaining.cs
│ ├── Person.cs
│ └── Unit.cs
├── Chapter04
│ ├── DictionaryGet.cs
│ ├── DishonestFunctions.cs
│ ├── Map.cs
│ ├── NameValueCollectionExt.cs
│ ├── Option.cs
│ ├── PetsInNeighbourhood.cs
│ └── SurveyOptionalAge.cs
├── Chapter05
│ └── Boc
│ │ ├── Functional.cs
│ │ └── Imperative.cs
├── Chapter06
│ ├── Aggregate.cs
│ ├── Boc
│ │ ├── Commands.cs
│ │ ├── EitherBasedAPIs
│ │ │ ├── BookTransferController.cs
│ │ │ ├── InstrumentsController.cs
│ │ │ └── Result.cs
│ │ ├── Errors.cs
│ │ ├── Particularized
│ │ │ └── BookTransferController.cs
│ │ └── TransferOnHandler
│ │ │ ├── v1 Skeleton.cs
│ │ │ └── v2 ValidationMethods.cs
│ ├── CookFavouriteFood.cs
│ ├── SimpleUsage.cs
│ ├── Unbiased.cs
│ └── Where.cs
├── Chapter07
│ ├── Boc
│ │ ├── FP
│ │ │ ├── Controller.cs
│ │ │ ├── ControllerActivator.cs
│ │ │ └── Sql.cs
│ │ └── OOP
│ │ │ ├── Controller.cs
│ │ │ ├── Repository.cs
│ │ │ └── Validators
│ │ │ ├── BicCodeValidator.cs
│ │ │ ├── DateNotPastValidator.cs
│ │ │ └── IbanValidator.cs
│ ├── ConnectionStringExt.cs
│ ├── DbQueries
│ │ ├── ConnectionString.cs
│ │ ├── EmployeeLookup.cs
│ │ └── SqlTemplate.cs
│ ├── Greetings.cs
│ ├── TypeInference.cs
│ └── ValidationStrategies.cs
├── Chapter08
│ ├── LINQ
│ │ ├── EnumerableExamples.cs
│ │ ├── OptionExamples.cs
│ │ └── OptionT.cs
│ ├── MapBinaryFuncExample.cs
│ ├── PhoneNumber.cs
│ ├── Traversables.cs
│ └── TypeInference_Map.cs
├── Chapter09
│ ├── Boc
│ │ ├── Account_Imutable.cs
│ │ ├── Account_Mutable.cs
│ │ ├── CurrencyCode.cs
│ │ ├── Transaction.cs
│ │ └── WithFSharp.cs
│ ├── Circle.cs
│ ├── Coyo.cs
│ ├── DateTime.cs
│ ├── ImmutableCollections.cs
│ ├── Introductory.cs
│ ├── LinkedList.cs
│ ├── Monkeys.cs
│ ├── TreeMap.cs
│ └── UnknownRewardTypeException.cs
├── Chapter10
│ ├── CommandExt.cs
│ ├── Commands.cs
│ ├── Controller.cs
│ ├── Data
│ │ ├── IEventStore.cs
│ │ └── InMemoryEventStore.cs
│ ├── Domain
│ │ ├── Account.cs
│ │ └── AccountState.cs
│ ├── Events.cs
│ ├── Query
│ │ └── AccountStatement.cs
│ └── Transitions
│ │ ├── Account.cs
│ │ ├── Controller.cs
│ │ └── Transition.cs
├── Chapter11
│ ├── Func_Map.cs
│ ├── Identity.cs
│ ├── Middleware
│ │ ├── DbLogger.cs
│ │ └── Middleware.cs
│ ├── Reader.cs
│ ├── Tap.cs
│ └── Try.cs
├── Chapter12
│ ├── CurrencyLookup.cs
│ ├── Generator.cs
│ ├── ListNumbering.cs
│ ├── Trade.cs
│ └── TreeNumbering.cs
├── Chapter13
│ ├── Applicative.cs
│ ├── Boc
│ │ ├── FxController.cs
│ │ └── MakeTransferController.cs
│ ├── CurrencyLookup.cs
│ ├── Retry.cs
│ └── ValidationStrategies.cs
├── Chapter14
│ ├── CreatingObservables.cs
│ ├── CurrencyLookup.cs
│ ├── ObservableExt.cs
│ ├── Operators.cs
│ └── VoidContinuations.cs
├── Chapter15
│ ├── Agents
│ │ ├── Counter.cs
│ │ ├── CurrencyLookup.cs
│ │ ├── IdealizedAgent.cs
│ │ └── PingPongAgents.cs
│ └── Boc
│ │ ├── AccountProcess.cs
│ │ ├── AccountRegistry.cs
│ │ ├── ControllerActivator.cs
│ │ ├── MakeTransferController.cs
│ │ └── Tests.cs
├── Examples.csproj
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ └── launchSettings.json
├── Startup.cs
├── appsettings.json
└── web.config
├── Exercises
├── Chapter01
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter02
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter03
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter04
│ ├── Exercises.cs
│ ├── Person.cs
│ └── Solutions.cs
├── Chapter05
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter06
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter07
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter08
│ ├── Exercises.cs
│ └── Solutions.cs
├── Chapter09
│ ├── Exercises.cs
│ └── Solutions.cs
├── Exercises.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── LICENSE
├── LaYumba.Functional.Data
├── BinaryTree.cs
├── Bst.cs
├── LaYumba.Functional.Data.csproj
├── LinkedList.cs
└── Properties
│ └── AssemblyInfo.cs
├── LaYumba.Functional.Tests
├── Assertions.cs
├── BinaryTreeTest.cs
├── Coyo.cs
├── EnumerableExtTests.cs
├── FTest.cs
├── FuncExtTest.cs
├── IEnumerable
│ ├── FunctorLaws.cs
│ └── MonadLaws.cs
├── Immutable.cs
├── LaYumba.Functional.Tests.csproj
├── Option
│ ├── ApplicativeLaws.cs
│ ├── FunctorLaws.cs
│ ├── MonadLaws.cs
│ └── OptionTest.cs
├── Pattern.cs
├── Properties
│ └── AssemblyInfo.cs
├── Task.cs
├── TestUtils.cs
├── Try.cs
├── Unit.cs
└── Valid.cs
├── LaYumba.Functional
├── ActionExt.cs
├── Agent.cs
├── Atom.cs
├── Coyo.cs
├── Date.cs
├── Decimal.cs
├── DictionaryExt.cs
├── Double.cs
├── Either.cs
├── EmptyList.cs
├── Enum.cs
├── EnumerableExt.cs
├── Error.cs
├── Exceptional.cs
├── F.cs
├── Free.cs
├── FuncExt.cs
├── Identity.cs
├── Immutable.cs
├── Int.cs
├── LaYumba.Functional.csproj
├── Long.cs
├── MonadStacks
│ ├── TaskOptionMonad.cs
│ └── TaskValidationMonad.cs
├── NoneType.cs
├── ObservableExt.cs
├── Option.cs
├── Pattern.cs
├── Properties
│ └── AssemblyInfo.cs
├── Reader.cs
├── StatefulComputation.cs
├── String.cs
├── TaskExt.cs
├── Traversable
│ ├── IEnumerable.cs
│ ├── Option.cs
│ └── Validation.cs
├── Try.cs
├── TupleExt.cs
└── Validation.cs
├── NuGet.Config
├── README.md
├── VS2017.sln
├── cover.jpg
└── setupRepl.csx
/Boc.Domain/Boc.Domain.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard1.6
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Boc.Domain/Lib.fs:
--------------------------------------------------------------------------------
1 | namespace Boc.Domain.FSharp
2 | open System
3 |
4 | type AccountStatus =
5 | Requested | Active | Frozen | Dormant | Closed
6 |
7 | type CurrencyCode = string
8 |
9 | type Transaction = {
10 | Amount: decimal
11 | Description: string
12 | Date: DateTime
13 | }
14 |
15 | type AccountState = {
16 | Status: AccountStatus
17 | Currency: CurrencyCode
18 | AllowedOverdraft: decimal
19 | TransactionHistory: Transaction list
20 | }
21 |
22 | type AccountState with
23 |
24 | member this.WithStatus(status) =
25 | { this with Status = Active }
26 |
27 | member this.AddTransaction(trans) =
28 | { this with TransactionHistory = trans ::
29 | this.TransactionHistory}
30 |
--------------------------------------------------------------------------------
/Examples/Chapter01/Circle.cs:
--------------------------------------------------------------------------------
1 | using static System.Math;
2 | public class Circle
3 | {
4 | public Circle(double radius) { Radius = radius; }
5 |
6 | public double Radius { get; }
7 |
8 | public (double Circumference, double Area) Stats
9 | => (Circumference, Area);
10 |
11 | public double Circumference
12 | => PI * 2 * Radius;
13 |
14 | public double Area
15 | {
16 | get
17 | {
18 | double Square(double d) => Pow(d, 2);
19 | return PI * Square(Radius);
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/Examples/Chapter01/DbLogger/ConnectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Data.SqlClient;
4 | using static LaYumba.Functional.F;
5 |
6 | namespace Examples.Chapter1.DbLogger
7 | {
8 | public static class ConnectionHelper
9 | {
10 | public static R Connect
11 | (string connString, Func func)
12 | {
13 | using (var conn = new SqlConnection(connString))
14 | {
15 | conn.Open();
16 | return func(conn);
17 | }
18 | }
19 |
20 | public static R Transact
21 | (SqlConnection conn, Func f)
22 | {
23 | R r = default(R);
24 | using (var tran = conn.BeginTransaction())
25 | {
26 | r = f(tran);
27 | tran.Commit();
28 | }
29 | return r;
30 | }
31 | }
32 |
33 | public static class ConnectionHelper_V2
34 | {
35 | public static R Connect(string connString, Func func)
36 | => Using(new SqlConnection(connString)
37 | , conn => { conn.Open(); return func(conn); });
38 | }
39 | }
--------------------------------------------------------------------------------
/Examples/Chapter01/DbLogger/DbLogger_V1.cs:
--------------------------------------------------------------------------------
1 | using System.Data;
2 | using System.Data.SqlClient;
3 | using Dapper;
4 | using System.Collections.Generic;
5 | using System;
6 |
7 | namespace Examples.Chapter1.DbLogger
8 | {
9 | public class DbLogger_V1
10 | {
11 | string connString;
12 |
13 | public void Log(LogMessage msg)
14 | {
15 | using (var conn = new SqlConnection(connString))
16 | {
17 | conn.Open();
18 | int affectedRows = conn.Execute("sp_create_log"
19 | , msg, commandType: CommandType.StoredProcedure);
20 | }
21 | }
22 |
23 | public void DeleteOldLogs()
24 | {
25 | using (var conn = new SqlConnection(connString))
26 | {
27 | conn.Open();
28 | //conn.Execute($@"DELETE [Logs] WHERE [Timestamp] <
29 | // '{DateTime.Now.AddDays(-7).ToString("s")}'");
30 | conn.Execute("DELETE [Logs] WHERE [Timestamp] < @upTo"
31 | , param: new { upTo = 7.Days().Ago() });
32 | }
33 | }
34 |
35 | public IEnumerable GetLogs(DateTime since)
36 | {
37 | var sqlGetLogs = "SELECT * FROM [Logs] WHERE [Timestamp] > @since";
38 | using (var conn = new SqlConnection(connString))
39 | {
40 | return conn.Query(sqlGetLogs, new { since = since });
41 | }
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/Examples/Chapter01/DbLogger/DbLogger_V2.cs:
--------------------------------------------------------------------------------
1 | using Dapper;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Data;
5 |
6 | namespace Examples.Chapter1.DbLogger
7 | {
8 | using static ConnectionHelper;
9 |
10 | public class DbLogger_V2
11 | {
12 | string connString;
13 | string sqlDeleteLogs = "DELETE [Logs] WHERE [Timestamp] < @upTo";
14 |
15 | public void Log(LogMessage message)
16 | => Connect(connString, c => c.Execute("sp_create_log"
17 | , message, commandType: CommandType.StoredProcedure));
18 |
19 | public void DeleteOldLogs() => Connect(connString
20 | , c => c.Execute(sqlDeleteLogs, new {upTo = 7.Days().Ago()}));
21 |
22 | public IEnumerable GetLogs(DateTime since)
23 | => Connect(connString, c => c.Query(@"SELECT *
24 | FROM [Logs] WHERE [Timestamp] > @since", new { since = since }));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Examples/Chapter01/DbLogger/LogMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Examples.Chapter1.DbLogger
2 | {
3 | public class LogMessage { }
4 | }
--------------------------------------------------------------------------------
/Examples/Chapter01/DbLogger/TimeSpanExt.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples
4 | {
5 | public static class TimeSpanExt
6 | {
7 | public static TimeSpan Seconds(this int @this)
8 | => TimeSpan.FromSeconds(@this);
9 |
10 | public static TimeSpan Minutes(this int @this)
11 | => TimeSpan.FromMinutes(@this);
12 |
13 | public static TimeSpan Days(this int @this)
14 | => TimeSpan.FromDays(@this);
15 |
16 | public static DateTime Ago(this TimeSpan @this)
17 | => DateTime.UtcNow - @this;
18 | }
19 | }
--------------------------------------------------------------------------------
/Examples/Chapter01/Employee.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace Examples
3 | {
4 | public class Employee { public string LastName { get; }}
5 | }
6 |
--------------------------------------------------------------------------------
/Examples/Chapter01/Functions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Examples.Chapter0.Introduction
6 | {
7 | class Names
8 | {
9 | Comparison caseInsensitive = (x, y) => x.ToUpper().CompareTo(y.ToUpper());
10 |
11 | public void Sort(List names) => names.Sort(caseInsensitive);
12 | }
13 |
14 | class Names_Lambda
15 | {
16 | public void Sort(List names)
17 | => names.Sort((x, y) => x.ToUpper().CompareTo(y.ToUpper()));
18 | }
19 |
20 | public class Lambda_Closure
21 | {
22 | private List employees;
23 |
24 | public IEnumerable FindByName(string name)
25 | => employees.Where(e => e.LastName.StartsWith(name));
26 | }
27 |
28 | class Cache where T : class
29 | {
30 | public T Get(Guid id, Func onMiss)
31 | => Get(id) ?? onMiss();
32 |
33 | T Get(Guid id)
34 | {
35 | throw new NotImplementedException();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Examples/Chapter01/HOFs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter1
4 | {
5 | using static Console;
6 |
7 | static class HOFs
8 | {
9 | internal static void Run()
10 | {
11 | Func divide = (dividend, divisor) => dividend / divisor;
12 | WriteLine(divide(10, 2)); // => 5
13 |
14 | var divideBy = divide.SwapArgs();
15 | WriteLine(divideBy(2, 10)); // => 5
16 | }
17 |
18 | public static Func SwapArgs(this Func func)
19 | => (t2, t1) => func(t1, t2);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/Chapter01/Tenets.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using NUnit.Framework;
6 |
7 | namespace Examples.Chapter0.Introduction
8 | {
9 | using static Enumerable;
10 | using static Console;
11 |
12 | class Tenets
13 | {
14 | static void _main()
15 | {
16 | var ints = new List { 1, 2, 3, 4, 5 };
17 |
18 | foreach (var i in ints)
19 | WriteLine(i);
20 |
21 | ints.ForEach(WriteLine);
22 | }
23 | }
24 |
25 | public class MutationShouldBeAvoided
26 | {
27 | [Test]
28 | public void NoInPlaceUpdates()
29 | {
30 | var original = new[] { 5, 7, 1 };
31 | var sorted = original.OrderBy(x => x).ToList();
32 |
33 | Assert.AreEqual(new[] { 5, 7, 1 }, original);
34 | Assert.AreEqual(new[] { 1, 5, 7 }, sorted);
35 | }
36 |
37 | [Test]
38 | public void InPlaceUpdates()
39 | {
40 | var original = new List { 5, 7, 1 };
41 | original.Sort();
42 | Assert.AreEqual(new[] { 1, 5, 7 }, original);
43 | }
44 |
45 | public static void WithListItBreaks()
46 | {
47 | var nums = Range(-10000, 20001).Reverse().ToList();
48 |
49 | Action task1 = () => WriteLine(nums.Sum());
50 | Action task2 = () => { nums.Sort(); WriteLine(nums.Sum()); };
51 |
52 | Parallel.Invoke(task1, task2);
53 | }
54 |
55 | public static void WithIEnumerableItWorks()
56 | {
57 | var nums = Range(-10000, 20001).Reverse();
58 |
59 | Action task1 = () => WriteLine(nums.Sum());
60 | Action task2 = () => { nums.OrderBy(x => x); WriteLine(nums.Sum()); };
61 |
62 | Parallel.Invoke(task1, task2);
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/BicExistsValidator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Boc.Commands;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using NUnit.Framework;
6 | using System.Text.RegularExpressions;
7 |
8 | namespace Boc.Services.Validation
9 | {
10 | public sealed class BicFormatValidator : IValidator
11 | {
12 | static readonly Regex regex = new Regex("^[A-Z]{6}[A-Z1-9]{5}$");
13 |
14 | public bool IsValid(MakeTransfer t)
15 | => regex.IsMatch(t.Bic);
16 | }
17 |
18 | public sealed class BicExistsValidator_Skeleton : IValidator
19 | {
20 | readonly IEnumerable validCodes;
21 |
22 | public bool IsValid(MakeTransfer cmd)
23 | => validCodes.Contains(cmd.Bic);
24 | }
25 |
26 | public sealed class BicExistsValidator : IValidator
27 | {
28 | readonly Func> getValidCodes;
29 |
30 | public BicExistsValidator(Func> getValidCodes)
31 | {
32 | this.getValidCodes = getValidCodes;
33 | }
34 |
35 | public bool IsValid(MakeTransfer cmd)
36 | => getValidCodes().Contains(cmd.Bic);
37 | }
38 |
39 | public class BicExistsValidatorTest
40 | {
41 | static string[] validCodes = { "ABCDEFGJ123" };
42 |
43 | [TestCase("ABCDEFGJ123", ExpectedResult = true)]
44 | [TestCase("XXXXXXXXXXX", ExpectedResult = false)]
45 | public bool WhenBicNotFound_ThenValidationFails(string bic)
46 | => new BicExistsValidator(() => validCodes)
47 | .IsValid(new MakeTransfer { Bic = bic });
48 | }
49 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/Commands.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Boc.Commands
4 | {
5 | public abstract class Command
6 | {
7 | public DateTime Timestamp { get; set; }
8 |
9 | public T WithTimestamp(DateTime timestamp)
10 | where T : Command
11 | {
12 | T result = (T)MemberwiseClone();
13 | result.Timestamp = timestamp;
14 | return result;
15 | }
16 | }
17 |
18 | public class MakeTransfer : Command
19 | {
20 | public Guid DebitedAccountId { get; set; }
21 |
22 | public string Beneficiary { get; set; }
23 | public string Iban { get; set; }
24 | public string Bic { get; set; }
25 |
26 | public DateTime Date { get; set; }
27 | public decimal Amount { get; set; }
28 | public string Reference { get; set; }
29 | }
30 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/DateNotPastValidator.NotTestable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Boc.Commands;
3 | using NUnit.Framework;
4 |
5 | namespace Boc.Services.Validation.NotTestable
6 | {
7 | public class DateNotPastValidator : IValidator
8 | {
9 | public bool IsValid(MakeTransfer request)
10 | => DateTime.UtcNow.Date <= request.Date.Date;
11 | }
12 |
13 | public class DateNotPastValidator_Impure_Test
14 | {
15 | [Test]
16 | [Ignore("Demonstrates a test that is not repeatable")]
17 | public void WhenTransferDateIsFuture_ThenValidatorPasses()
18 | {
19 | var sut = new DateNotPastValidator();
20 | var transfer = new MakeTransfer { Date = new DateTime(2016, 12, 12) };
21 |
22 | var actual = sut.IsValid(transfer);
23 | Assert.AreEqual(true, actual);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/DateNotPastValidator.TestableWithInjectedValue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Boc.Commands;
3 |
4 | namespace Boc.Services.Validation.InjectValue
5 | {
6 | public class DateNotPastValidator : IValidator
7 | {
8 | DateTime Today { get; }
9 |
10 | public DateNotPastValidator(DateTime today)
11 | {
12 | this.Today = today;
13 | }
14 |
15 | public bool IsValid(MakeTransfer cmd)
16 | => Today <= cmd.Date.Date;
17 | }
18 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/DateNotPastValidator.TestableWithInterface.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using NUnit.Framework;
3 | using System;
4 |
5 | namespace Boc.Services.Validation.WithDI
6 | {
7 | // interface
8 | public interface IDateTimeService
9 | {
10 | DateTime UtcNow { get; }
11 | }
12 |
13 | // "real" implementation
14 | public class DefaultDateTimeService : IDateTimeService
15 | {
16 | public DateTime UtcNow => DateTime.UtcNow;
17 | }
18 |
19 | // testable class depends on interface
20 | public class DateNotPastValidator_Testable : IValidator
21 | {
22 | private readonly IDateTimeService clock;
23 |
24 | public DateNotPastValidator_Testable(IDateTimeService clock)
25 | {
26 | this.clock = clock;
27 | }
28 |
29 | public bool IsValid(MakeTransfer request)
30 | => clock.UtcNow.Date <= request.Date.Date;
31 | }
32 |
33 | // can be tested using fakes
34 | public class DateNotPastValidatorTest
35 | {
36 | static DateTime presentDate = new DateTime(2016, 12, 12);
37 |
38 | // "fake" implementation
39 | private class FakeDateTimeService : IDateTimeService
40 | {
41 | public DateTime UtcNow => presentDate;
42 | }
43 |
44 | // example-based unit test
45 | [Test]
46 | public void WhenTransferDateIsPast_ThenValidatorFails()
47 | {
48 | var sut = new DateNotPastValidator_Testable(new FakeDateTimeService());
49 | var transfer = new MakeTransfer { Date = presentDate.AddDays(-1) };
50 | Assert.AreEqual(false, sut.IsValid(transfer));
51 | }
52 |
53 | // parameterized unit test
54 | [TestCase(+1, ExpectedResult = true)]
55 | [TestCase(0, ExpectedResult = true)]
56 | [TestCase(-1, ExpectedResult = false)]
57 | public bool WhenTransferDateIsPast_ThenValidationFails(int offset)
58 | {
59 | var sut = new DateNotPastValidator_Testable(new FakeDateTimeService());
60 | var transferDate = presentDate.AddDays(offset);
61 | var transfer = new MakeTransfer { Date = transferDate };
62 |
63 | return sut.IsValid(transfer);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/Boc/IValidator.cs:
--------------------------------------------------------------------------------
1 | namespace Boc.Services
2 | {
3 | public interface IValidator
4 | {
5 | bool IsValid(T t);
6 | }
7 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_InfiniteSequence.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using LaYumba.Functional;
4 | using NUnit.Framework;
5 | using static System.Console;
6 |
7 | namespace Examples.Purity.ListFormatter.Parallel.InfiniteSequence
8 | {
9 | static class ListFormatter
10 | {
11 | static IEnumerable Naturals(int startingWith = 0)
12 | {
13 | while (true) yield return startingWith++;
14 | }
15 |
16 | public static IEnumerable Format(IEnumerable list)
17 | => list.AsParallel()
18 | .Select(StringExt.ToSentenceCase)
19 | .Zip(Naturals(startingWith: 1).AsParallel()
20 | , (s, i) => $"{i}. {s}");
21 |
22 | internal static void _main()
23 | {
24 | var size = 100000;
25 | var shoppingList = Enumerable.Range(1, size).Select(i => $"item{i}");
26 |
27 | ListFormatter.Format(shoppingList).ForEach(WriteLine);
28 |
29 | ReadKey();
30 | }
31 | }
32 |
33 |
34 | public class ZipListFormatterTests
35 | {
36 | [Test]
37 | public void ItWorksOnSingletonList()
38 | {
39 | var input = new[] { "coffee beans" };
40 | var output = ListFormatter.Format(input).ToList();
41 | Assert.AreEqual("1. Coffee beans", output[0]);
42 | }
43 |
44 | [Test]
45 | public void ItWorksOnAVeryLongList()
46 | {
47 | var size = 100000;
48 | var input = Enumerable.Range(1, size).Select(i => $"item{i}");
49 | var output = ListFormatter.Format(input).ToList();
50 | Assert.That(output[size - 1].StartsWith("100000. Item"));
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_Instance.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using NUnit.Framework;
4 | using static System.Console;
5 |
6 | namespace Examples.Purity.ListFormatter.Instance
7 | {
8 | class ListFormatter
9 | {
10 | int counter;
11 |
12 | string PrependCounter(string s) => $"{++counter}. {s}";
13 |
14 | public List Format(List list)
15 | => list
16 | .Select(StringExt.ToSentenceCase)
17 | .Select(PrependCounter)
18 | .ToList();
19 |
20 | internal static void _main()
21 | {
22 | var shoppingList = new List { "coffee beans", "BANANAS", "Dates" };
23 |
24 | new ListFormatter()
25 | .Format(shoppingList)
26 | .ForEach(WriteLine);
27 |
28 | Read();
29 | }
30 | }
31 |
32 | public class ListFormatter_InstanceTests
33 | {
34 | [Test] public void ItWorksOnSingletonList()
35 | {
36 | var input = new List { "coffee beans" };
37 | var output = new ListFormatter().Format(input);
38 | Assert.AreEqual("1. Coffee beans", output[0]);
39 | }
40 |
41 | [Test]
42 | public void ItWorksOnLongerList()
43 | {
44 | var input = new List { "coffee beans", "BANANAS" };
45 | var output = new ListFormatter().Format(input);
46 | Assert.AreEqual("1. Coffee beans", output[0]);
47 | Assert.AreEqual("2. Bananas", output[1]);
48 | }
49 |
50 | [Test]
51 | public void ItWorksOnAVeryLongList()
52 | {
53 | var size = 100000;
54 | var input = Enumerable.Range(1, size).Select(i => $"item{i}").ToList();
55 | var output = new ListFormatter().Format(input);
56 | Assert.AreEqual("100000. Item100000", output[size - 1]);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_ParNaive.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 |
6 | namespace Examples.Purity.ListFormatter.Parallel.Naive
7 | {
8 | class ListFormatter
9 | {
10 | int counter;
11 |
12 | Numbered ToNumberedValue(T t) => new Numbered(t, ++counter);
13 |
14 | // possible fix, using lock
15 | // Numbered ToNumberedValue(T t) => new Numbered(t, Interlocked.Increment(ref counter));
16 |
17 | string Render(Numbered s) => $"{s.Number}. {s.Value}";
18 |
19 | public List Format(IEnumerable list)
20 | => list.AsParallel()
21 | .Select(StringExt.ToSentenceCase)
22 | .Select(ToNumberedValue)
23 | .OrderBy(n => n.Number)
24 | .Select(Render)
25 | .ToList();
26 |
27 | public void Main()
28 | {
29 | var size = 100000;
30 | var shoppingList = Enumerable.Range(1, size).Select(i => $"item{i}");
31 |
32 | new ListFormatter()
33 | .Format(shoppingList)
34 | .ForEach(Console.WriteLine);
35 |
36 | Console.Read();
37 | }
38 |
39 | }
40 |
41 |
42 | public class ParallelListFormatterTests
43 | {
44 | [Test]
45 | public void ItWorksOnSingletonList()
46 | {
47 | var input = new[] { "coffee beans" };
48 | var output = new ListFormatter().Format(input);
49 | Assert.That("1. Coffee beans" == output[0]);
50 | }
51 |
52 | //Expected string length 20 but was 19. Strings differ at index 0.
53 | //Expected: "1000000. Item1000000"
54 | //But was: "956883. Item1000000"
55 | //-----------^
56 | [Ignore("Tests will fail because of lost updates")]
57 | [Test]
58 | public void ItWorksOnAVeryLongList()
59 | {
60 | var size = 100000;
61 | var input = Enumerable.Range(1, size).Select(i => $"item{i}");
62 | var output = new ListFormatter().Format(input);
63 | Assert.AreEqual("100000. Item100000", output[size - 1]);
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_ParZip.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using NUnit.Framework;
4 |
5 | namespace Examples.Purity.ListFormatter.Parallel.WithRange
6 | {
7 | using static ParallelEnumerable;
8 |
9 | static class ListFormatter
10 | {
11 | public static List Format(List list)
12 | => list.AsParallel()
13 | .Select(StringExt.ToSentenceCase)
14 | .Zip(Range(1, list.Count), (s, i) => $"{i}. {s}")
15 | .ToList();
16 | }
17 |
18 |
19 | public class RangeListFormatterTests
20 | {
21 | [Test]
22 | public void ItWorksOnSingletonList()
23 | {
24 | var input = new List { "coffee beans" };
25 | var output = ListFormatter.Format(input).ToList();
26 | Assert.AreEqual("1. Coffee beans", output[0]);
27 | }
28 |
29 | [Test]
30 | public void ItWorksOnAVeryLongList()
31 | {
32 | var size = 100000;
33 | var input = Enumerable.Range(1, size).Select(i => $"item{i}").ToList();
34 | var output = ListFormatter.Format(input);
35 | Assert.That(output[size - 1].StartsWith("100000. Item"));
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_Static.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using NUnit.Framework;
4 | using static System.Console;
5 |
6 | namespace Examples.Purity.ListFormatter.Static
7 | {
8 | public static class ListFormatter
9 | {
10 | static int counter;
11 |
12 | static string PrependCounter(string s) => $"{++counter}. {s}";
13 |
14 | public static List Format(List list)
15 | => list
16 | .Select(StringExt.ToSentenceCase)
17 | .Select(PrependCounter)
18 | .ToList();
19 |
20 | internal static void _main()
21 | {
22 | var shoppingList = new List { "coffee beans", "BANANAS", "Dates" };
23 |
24 | ListFormatter
25 | .Format(shoppingList)
26 | .ForEach(WriteLine);
27 |
28 | Read();
29 | }
30 | }
31 |
32 | // If ListFormatter is static, order of execution matters
33 | // Tests must not have any order-of-run dependency.
34 | [Ignore("Tests will fail because they are not isolated")]
35 | public class ListFormatterTests
36 | {
37 | [Test] public void ItWorksOnSingletonList()
38 | {
39 | var input = new List { "coffee beans" };
40 | var output = ListFormatter.Format(input);
41 | Assert.AreEqual("1. Coffee beans", output[0]);
42 | }
43 |
44 | [Test]
45 | public void ItWorksOnLongerList()
46 | {
47 | var input = new List { "coffee beans", "BANANAS" };
48 | var output = ListFormatter.Format(input);
49 | Assert.AreEqual("1. Coffee beans", output[0]);
50 | Assert.AreEqual("2. Bananas", output[1]);
51 | }
52 |
53 | [Test]
54 | public void ItWorksOnAVeryLongList()
55 | {
56 | var size = 100000;
57 | var input = Enumerable.Range(1, size).Select(i => $"item{i}").ToList();
58 | var output = ListFormatter.Format(input);
59 | Assert.AreEqual("100000. Item100000", output[size - 1]);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/ListFormatter_Zip.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Examples.Purity.ListFormatter.WithZip
5 | {
6 | using static Enumerable;
7 |
8 | static class ListFormatter
9 | {
10 | public static List Format__(List list)
11 | => list
12 | .Select(StringExt.ToSentenceCase)
13 | .Zip(Range(1, list.Count), (s, i) => $"{i}. {s}")
14 | .ToList();
15 |
16 | public static List Format(List list)
17 | {
18 | var left = list.Select(StringExt.ToSentenceCase);
19 | var right = Range(1, list.Count);
20 | var zipped = Enumerable.Zip(left, right, (s, i) => $"{i}. {s}");
21 | return zipped.ToList();
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/Numbered.cs:
--------------------------------------------------------------------------------
1 | namespace Examples
2 | {
3 | public class Numbered
4 | {
5 | public Numbered(T Value, int Number)
6 | {
7 | this.Value = Value;
8 | this.Number = Number;
9 | }
10 |
11 | public int Number { get; set; }
12 | public T Value { get; set; }
13 |
14 | public override string ToString()
15 | => $"({Number}, {Value})";
16 |
17 | public static Numbered Create(T Value, int Number)
18 | => new Numbered(Value, Number);
19 | }
20 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/ListFormatter/StringExt.cs:
--------------------------------------------------------------------------------
1 | namespace Examples.Purity
2 | {
3 | public static class StringExt
4 | {
5 | public static string ToSentenceCase(this string s)
6 | => s.ToUpper()[0] + s.ToLower().Substring(1);
7 | }
8 | }
--------------------------------------------------------------------------------
/Examples/Chapter02/MutatingArguments.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Examples.Chapter2
6 | {
7 | class MutatingArguments
8 | {
9 | decimal RecomputeTotal_MutatingArguments(Order order, List linesToDelete)
10 | {
11 | var result = 0m;
12 | foreach (var line in order.OrderLines)
13 | if (line.Quantity == 0) linesToDelete.Add(line);
14 | else result += line.Product.Price * line.Quantity;
15 | return result;
16 | }
17 |
18 | (decimal, IEnumerable) RecomputeTotal_NoSideEffects(Order order)
19 | => ( order.OrderLines.Sum(l => l.Product.Price * l.Quantity)
20 | , order.OrderLines.Where(l => l.Quantity == 0));
21 |
22 | class Product
23 | {
24 | public decimal Price { get; }
25 | }
26 |
27 | class OrderLine
28 | {
29 | public Product Product { get; }
30 | public int Quantity { get; }
31 | }
32 |
33 | class Order
34 | {
35 | public IEnumerable OrderLines { get; }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Examples/Chapter02/Zip.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace Examples.Purity
5 | {
6 | using static Console;
7 |
8 | public static class Zip
9 | {
10 | internal static void _main()
11 | {
12 | var sentences = Enumerable.Zip(
13 | new[] {1, 2, 3},
14 | new[] {"ichi", "ni", "san"},
15 | (number, name) => $"In Japanese, {number} is: {name}");
16 |
17 | foreach (var sentence in sentences)
18 | WriteLine(sentence);
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/Chapter03/Age.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 |
4 | namespace Examples.Chapter3
5 | {
6 | using static F;
7 |
8 | public struct Age
9 | {
10 | private int Value { get; }
11 | private Age(int value)
12 | {
13 | if (!IsValid(value))
14 | throw new ArgumentException($"{value} is not a valid age");
15 |
16 | Value = value;
17 | }
18 |
19 | private static bool IsValid(int age)
20 | => 0 <= age && age < 120;
21 |
22 | public static Option Of(int age)
23 | => IsValid(age) ? Some(new Age(age)) : None;
24 |
25 | public static bool operator <(Age l, Age r) => l.Value < r.Value;
26 | public static bool operator >(Age l, Age r) => l.Value > r.Value;
27 |
28 | public static bool operator <(Age l, int r) => l < new Age(r);
29 | public static bool operator >(Age l, int r) => l > new Age(r);
30 |
31 | public override string ToString() => Value.ToString();
32 | }
33 |
34 | enum Risk { Low, Medium, High }
35 |
36 | public class Bind_Example
37 | {
38 | internal static void _main()
39 | {
40 | Func> parseAge = s
41 | => Int.Parse(s).Bind(Age.Of);
42 |
43 | parseAge("26"); // => Some(26)
44 | parseAge("notAnAge"); // => None
45 | parseAge("11111"); // => None
46 | }
47 |
48 | internal void RunExamples()
49 | {
50 | CalculateRiskProfile_Dynamic("hello"); // compiles, but causes runtime exception
51 | //CalculateRiskProfile_Int("hello"); // error is caught at compile time
52 | }
53 |
54 | Risk CalculateRiskProfile_Dynamic(dynamic age)
55 | => (age < 60) ? Risk.Low : Risk.Medium;
56 |
57 |
58 | Risk CalculateRiskProfile_Int(int age)
59 | => (age < 60) ? Risk.Low : Risk.Medium;
60 |
61 | Risk CalculateRiskProfile_Throws(int age)
62 | {
63 | if (age < 0 || 120 <= age)
64 | throw new ArgumentException($"{age} is not a valid age");
65 |
66 | return (age < 60) ? Risk.Low : Risk.Medium;
67 | }
68 |
69 | Risk CalculateRiskProfile(Age age)
70 | => (age < 60) ? Risk.Low : Risk.Medium;
71 |
72 | Risk CalculateRiskProfile(Age age, bool smoker)
73 | => (age < 60)
74 | ? Risk.Low
75 | : (smoker) ? Risk.High : Risk.Medium;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Examples/Chapter03/ConfigurationExt.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 | using System;
3 |
4 | using LaYumba.Functional;
5 | using static LaYumba.Functional.F;
6 | using System.Linq;
7 | using NUnit.Framework;
8 |
9 | namespace Boc
10 | {
11 | static class ConfigurationExt
12 | {
13 | public static Option Lookup
14 | (this IConfigurationRoot config, params string[] path)
15 | => (T)Convert.ChangeType(config.GetSection(path).Value, typeof(T));
16 |
17 | public static Option Lookup
18 | (this IConfigurationRoot config, params string[] path)
19 | => config.GetSection(path).Value;
20 |
21 | public static IConfigurationSection GetSection
22 | (this IConfigurationRoot config, params string[] path)
23 | => (IConfigurationSection)path.Aggregate(config as IConfiguration, (parent, name) => parent.GetSection(name));
24 | }
25 |
26 | static class ConfigurationExtTest
27 | {
28 | static IConfigurationRoot GetConfig()
29 | {
30 | var builder = new ConfigurationBuilder();
31 | builder.AddInMemoryCollection();
32 | var config = builder.Build();
33 | config["somekey"] = "somevalue";
34 | return config;
35 | }
36 |
37 | [Test]
38 | public static void WhenAValueIsAvailable_LookupReturnsSome() => Assert.AreEqual(
39 | actual: GetConfig().Lookup("somekey"),
40 | expected: Some("somevalue"));
41 |
42 | [Test]
43 | public static void WhenAValueIsNotAvailable_LookupReturnsNone() => Assert.AreEqual(
44 | actual: GetConfig().Lookup("_"),
45 | expected: None);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Examples/Chapter03/Earnings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using LaYumba.Functional;
5 | using NUnit.Framework;
6 |
7 | namespace Examples.Chapter3
8 | {
9 | using static F;
10 |
11 | public static class PopulationStatistics
12 | {
13 | public static decimal AverageEarnings(this IEnumerable population)
14 | => population
15 | .Average(p => p.Earnings);
16 |
17 | public static IEnumerable RichestQuartile(this List population)
18 | => population
19 | .OrderByDescending(p => p.Earnings)
20 | .Take(population.Count / 4);
21 |
22 | public static decimal AverageEarningsOfRichestQuartile(List population)
23 | => population
24 | .OrderByDescending(p => p.Earnings)
25 | .Take(population.Count / 4)
26 | .Select(p => p.Earnings)
27 | .Average();
28 | }
29 |
30 |
31 | public class TestEarnings
32 | {
33 | [TestCase(ExpectedResult = 75000)]
34 | public decimal AverageEarningsOfRichestQuartile()
35 | => SamplePopulation
36 | .RichestQuartile()
37 | .AverageEarnings();
38 |
39 | private static List SamplePopulation
40 | => Range(1, 8).Map(i => new Person { Earnings = i * 10000 }).ToList();
41 |
42 | [TestCase(ExpectedResult = 75000)]
43 | public decimal AverageEarningsOfRichestQuartile_()
44 | {
45 | var population = Range(1, 8)
46 | .Select(i => new Person { Earnings = i * 10000 })
47 | .ToList();
48 |
49 | return PopulationStatistics
50 | .AverageEarningsOfRichestQuartile(population);
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/Examples/Chapter03/FunctionComposition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter3
4 | {
5 | using static Console;
6 |
7 | public static class FunctionComposition
8 | {
9 | static string AbbreviateName(Person p)
10 | => Abbreviate(p.FirstName) + Abbreviate(p.LastName);
11 |
12 | static string AppendDomain(string localPart)
13 | => $"{localPart}@manning.com";
14 |
15 | static string Abbreviate(string s)
16 | => s.Substring(0, 2).ToLower();
17 |
18 | internal static void _main()
19 | {
20 | Func emailFor =
21 | p => AppendDomain(AbbreviateName(p));
22 |
23 | var joe = new Person("Joe", "Bloggs");
24 | var email = emailFor(joe); // => jobl@manning.com
25 |
26 | WriteLine(email);
27 | ReadKey();
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Examples/Chapter03/Instrumentation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using LaYumba.Functional;
4 | using Microsoft.Extensions.Logging;
5 | using Unit = System.ValueTuple;
6 |
7 | namespace Examples
8 | {
9 | public static class Instrumentation
10 | {
11 | public static T Time(ILogger log, string op, Func f)
12 | {
13 | var sw = new Stopwatch();
14 | sw.Start();
15 |
16 | T t = f();
17 |
18 | sw.Stop();
19 | log.LogDebug($"{op} took {sw.ElapsedMilliseconds}ms");
20 | return t;
21 | }
22 |
23 | public static T Trace(ILogger log, string op, Func f)
24 | {
25 | log.LogTrace($"Entering {op}");
26 | T t = f();
27 | log.LogTrace($"Leaving {op}");
28 | return t;
29 | }
30 |
31 | public static T Trace(Action log, string op, Func f)
32 | {
33 | log($"Entering {op}");
34 | T t = f();
35 | log($"Leaving {op}");
36 | return t;
37 | }
38 |
39 | public static T Time(string op, Func f)
40 | {
41 | var sw = new Stopwatch();
42 | sw.Start();
43 |
44 | T t = f();
45 |
46 | sw.Stop();
47 | Console.WriteLine($"{op} took {sw.ElapsedMilliseconds}ms");
48 | return t;
49 | }
50 |
51 | /* duplication!
52 | public static void Time(string op, Action act)
53 | {
54 | var sw = new Stopwatch();
55 | sw.Start();
56 |
57 | act();
58 |
59 | sw.Stop();
60 | Console.WriteLine($"{op} took {sw.ElapsedMilliseconds}ms");
61 | }*/
62 |
63 | public static void Time(string op, Action act)
64 | => Time(op, act.ToFunc());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Examples/Chapter03/MethodChaining.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter3
4 | {
5 | public static class MethodChaining
6 | {
7 | static string AbbreviateName(this Person p)
8 | => Abbreviate(p.FirstName) + Abbreviate(p.LastName);
9 |
10 | static string AppendDomain(this string localPart)
11 | => $"{localPart}@manning.com";
12 |
13 | static string Abbreviate(string s)
14 | => s.Substring(0, 2).ToLower();
15 |
16 | internal static void _main()
17 | {
18 | var joe = new Person("Joe", "Bloggs");
19 | var email = joe.AbbreviateName().AppendDomain();
20 | // => jobl@manning.com
21 |
22 | Console.WriteLine(email);
23 | Console.ReadKey();
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Examples/Chapter03/Person.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 |
3 | namespace Examples.Chapter3
4 | {
5 | public class Person
6 | {
7 | public string FirstName { get; }
8 | public string LastName { get; }
9 |
10 | public decimal Earnings { get; set; }
11 | public Option Age { get; set; }
12 |
13 | public Person() { }
14 |
15 | public Person(string firstName, string lastName)
16 | {
17 | FirstName = firstName;
18 | LastName = lastName;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/Chapter03/Unit.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using LaYumba.Functional;
4 |
5 | namespace Examples.Chapter3
6 | {
7 | using static F;
8 |
9 | public class UnitExample
10 | {
11 | static void _main()
12 | {
13 | // call the overload that takes a Func
14 | var contents = Instrumentation.Time("reading from file.txt"
15 | , () => File.ReadAllText("file.txt"));
16 |
17 | // explicitly call with Func
18 | Instrumentation.Time("reading from file.txt", () =>
19 | {
20 | File.AppendAllText("file.txt", "New content!", Encoding.UTF8);
21 | return Unit();
22 | });
23 |
24 | // call the overload that takes an Action
25 | Instrumentation.Time("reading from file.txt"
26 | , () => File.AppendAllText("file.txt", "New content!", Encoding.UTF8));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Examples/Chapter04/DictionaryGet.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Examples.Chapter2
4 | {
5 | public static class Examples
6 | {
7 | public static string Get1(IDictionary dictionary, string key)
8 | {
9 | if (dictionary.ContainsKey(key))
10 | return dictionary[key];
11 | return null;
12 | }
13 |
14 | public static string Get2(IDictionary dictionary, string key)
15 | {
16 | string result;
17 | dictionary.TryGetValue(key, out result);
18 | return result;
19 | }
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/Examples/Chapter04/DishonestFunctions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Specialized;
4 | using static System.Console;
5 |
6 | namespace Examples.Chapter3.Option
7 | {
8 | class DishonestFunctions
9 | {
10 | ///
11 | /// prints:
12 | /// green!
13 | /// KeyNotFoundException
14 | ///
15 | internal static void _main()
16 | {
17 | try
18 | {
19 | var empty = new NameValueCollection();
20 | var green = empty["green"];
21 | WriteLine("green!");
22 |
23 | var alsoEmpty = new Dictionary();
24 | var blue = alsoEmpty["blue"];
25 | WriteLine("blue!");
26 | }
27 | catch (Exception ex)
28 | {
29 | WriteLine(ex.GetType().Name);
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Examples/Chapter04/Map.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using static System.Console;
4 | using String = LaYumba.Functional.String;
5 |
6 | namespace Examples.Chapter4
7 | {
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using static F;
11 |
12 | public class Examples
13 | {
14 | public static void List_Map()
15 | {
16 | Func plus3 = x => x + 3;
17 |
18 | var a = new[] { 2, 4, 6 };
19 | // => [2, 4, 6]
20 |
21 | var b = a.Map(plus3);
22 | // => [5, 7, 9]
23 | }
24 |
25 | public static void List_ForEach()
26 | {
27 | Enumerable.Range(1, 5).ForEach(Console.Write);
28 | }
29 |
30 | internal static void _main()
31 | {
32 | Option name = Some("Enrico");
33 |
34 | name
35 | .Map(String.ToUpper)
36 | .ForEach(WriteLine);
37 |
38 | IEnumerable names = new[] { "Constance", "Brunhilde" };
39 |
40 | names
41 | .Map(String.ToUpper)
42 | .ForEach(WriteLine);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Examples/Chapter04/NameValueCollectionExt.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using System.Collections.Specialized;
3 | using static LaYumba.Functional.F;
4 |
5 | namespace Examples.Chapter4
6 | {
7 | public static class NameValueCollectionExt
8 | {
9 | public static Option Lookup
10 | (this NameValueCollection @this, string key)
11 | => @this[key];
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Examples/Chapter04/Option.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 |
3 | namespace Examples.Chapter1
4 | {
5 | using static F;
6 | using System;
7 | using static System.Console;
8 |
9 | class Option_Match_Example
10 | {
11 | internal static void _main()
12 | {
13 | string _ = null, john = "John";
14 |
15 | Greet(john); // prints: hello, John
16 | Greet(_); // prints: sorry, who?
17 |
18 | ReadKey();
19 | }
20 |
21 | static void Greet(string name)
22 | => WriteLine(GreetingFor(name));
23 |
24 | static string GreetingFor(string name)
25 | => Some(name).Match(
26 | Some: n => $"hello, {n}",
27 | None: () => "sorry, who?");
28 | }
29 |
30 | class Option_Map_Example
31 | {
32 | internal static void _main()
33 | {
34 | Func greet = name => $"hello, {name}";
35 | string _ = null, john = "John";
36 |
37 | Some(john).Map(greet); // => Some("hello, John")
38 | Some(_).Map(greet); // => None
39 | }
40 | }
41 |
42 | class Person
43 | {
44 | public string Name;
45 | public Option Relationship;
46 | }
47 |
48 | class Relationship
49 | {
50 | public string Type;
51 | public Person Partner;
52 | }
53 |
54 | static class Option_Match_Example2
55 | {
56 | internal static void _main()
57 | {
58 | Person grace = new Person { Name = "Grace" }
59 | , dimitry = new Person { Name = "Dimitry" }
60 | , armin = new Person { Name = "Armin" };
61 |
62 | grace.Relationship = new Relationship {
63 | Type = "going out", Partner = dimitry };
64 |
65 | WriteLine(grace.RelationshipStatus());
66 | // prints: Grace is going out with Dimitry
67 |
68 | WriteLine(armin.RelationshipStatus());
69 | // prints: Armin is single
70 |
71 | ReadKey();
72 | }
73 |
74 | static string RelationshipStatus(this Person p)
75 | => p.Relationship.Match(
76 | Some: r => $"{p.Name} is {r.Type} with {r.Partner.Name}",
77 | None: () => $"{p.Name} is single");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Examples/Chapter04/PetsInNeighbourhood.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using LaYumba.Functional;
4 | using static LaYumba.Functional.F;
5 |
6 | namespace Examples.Bind
7 | {
8 | class PetsInNeighbourhood
9 | {
10 | internal static void _main_1()
11 | {
12 | var neighbours = new[]
13 | {
14 | new {Name = "John", Pets = new Pet[] {"Fluffy", "Thor"}},
15 | new {Name = "Tim", Pets = new Pet[] {}},
16 | new {Name = "Carl", Pets = new Pet[] {"Sybil"}},
17 | };
18 |
19 | IEnumerable> nested = neighbours.Map(n => n.Pets);
20 | IEnumerable flat = neighbours.Bind(n => n.Pets);
21 | }
22 |
23 | class Neighbour
24 | {
25 | public string Name { get; set; }
26 | public IEnumerable Pets { get; set; } = new Pet[] { };
27 | }
28 | }
29 |
30 | internal class Pet
31 | {
32 | private readonly string name;
33 |
34 | private Pet(string name)
35 | {
36 | this.name = name;
37 | }
38 |
39 | public static implicit operator Pet(string name)
40 | => new Pet(name);
41 | }
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/Examples/Chapter04/SurveyOptionalAge.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using LaYumba.Functional;
4 | using static LaYumba.Functional.F;
5 | using Examples.Chapter3;
6 | using static System.Console;
7 |
8 | namespace Examples.Bind
9 | {
10 | public static class AskForValidAgeAndPrintFlatteringMessage
11 | {
12 | public static void _main()
13 | => WriteLine($"Only {ReadAge()}! That's young!");
14 |
15 | static Option ParseAge(string s)
16 | => Int.Parse(s).Bind(Age.Of);
17 |
18 | static Age ReadAge()
19 | => ParseAge(Prompt("Please enter your age")).Match(
20 | () => ReadAge(),
21 | (age) => age);
22 |
23 | static string Prompt(string prompt)
24 | {
25 | WriteLine(prompt);
26 | return ReadLine();
27 | }
28 | }
29 |
30 | class SurveyOptionalAge
31 | {
32 | class Person
33 | {
34 | public Option Age { get; set; }
35 | }
36 |
37 | static IEnumerable Population => new[]
38 | {
39 | new Person { Age = Some(33) },
40 | new Person { }, // this person did not disclose her age
41 | new Person { Age = Some(37) },
42 | };
43 |
44 | internal static void _main()
45 | {
46 | var optionalAges = Population.Map(p => p.Age);
47 | // => [Some(33), None, Some(37)]
48 |
49 | var statedAges = Population.Bind(p => p.Age);
50 | // => [33, 37]
51 |
52 | var averageAge = statedAges.Average();
53 | // => 35
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Examples/Chapter05/Boc/Functional.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System;
3 | using LaYumba.Functional;
4 | using static LaYumba.Functional.F;
5 | using Boc.Commands;
6 | using Boc.Services;
7 |
8 | namespace Boc.Chapter5
9 | {
10 | // top-level workflow
11 |
12 | public class Chapter5_TransfersController : Controller
13 | {
14 | IValidator validator;
15 | IRepository accounts;
16 | ISwiftService swift;
17 |
18 | public void MakeTransfer([FromBody] MakeTransfer transfer)
19 | => Some(transfer)
20 | .Map(Normalize)
21 | .Where(validator.IsValid)
22 | .ForEach(Book);
23 |
24 | void Book(MakeTransfer transfer)
25 | => accounts.Get(transfer.DebitedAccountId)
26 | .Bind(account => account.Debit(transfer.Amount))
27 | .ForEach(newState =>
28 | {
29 | accounts.Save(transfer.DebitedAccountId, newState);
30 | swift.Wire(transfer, newState);
31 | });
32 |
33 | MakeTransfer Normalize(MakeTransfer request)
34 | => request; // remove whitespace, toUpper, etc.
35 | }
36 |
37 |
38 | // domain model
39 |
40 | public class AccountState
41 | {
42 | public decimal Balance { get; }
43 | public AccountState(decimal balance) { Balance = balance; }
44 | }
45 |
46 | public static class Account
47 | {
48 | public static Option Debit
49 | (this AccountState acc, decimal amount)
50 | => (acc.Balance < amount)
51 | ? None
52 | : Some(new AccountState(acc.Balance - amount));
53 | }
54 |
55 |
56 | // dependencies
57 |
58 | public interface IRepository
59 | {
60 | Option Get(Guid id);
61 | void Save(Guid id, T t);
62 | }
63 |
64 | interface ISwiftService
65 | {
66 | void Wire(MakeTransfer transfer, AccountState account);
67 | }
68 | }
--------------------------------------------------------------------------------
/Examples/Chapter05/Boc/Imperative.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System;
3 | using Boc.Commands;
4 | using Boc.Services;
5 |
6 | namespace Boc.Chapter5.Imperative
7 | {
8 | public class Chapter5_TransfersController : Controller
9 | {
10 | IValidator validator;
11 |
12 | public void MakeTransfer([FromBody] MakeTransfer transfer)
13 | {
14 | if (validator.IsValid(transfer))
15 | Book(transfer);
16 | }
17 |
18 | void Book(MakeTransfer transfer) { throw new NotImplementedException(); }
19 | }
20 |
21 | public class Account
22 | {
23 | public decimal Balance { get; private set; }
24 |
25 | public Account(decimal balance) { Balance = balance; }
26 |
27 | public void Debit(decimal amount)
28 | {
29 | if (Balance < amount)
30 | throw new InvalidOperationException("Insufficient funds");
31 |
32 | Balance -= amount;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Examples/Chapter06/Aggregate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using LaYumba.Functional;
5 |
6 | namespace Examples.Chapter6
7 | {
8 | using static F;
9 |
10 | public class Aggregate
11 | {
12 | public static void _main()
13 | {
14 | var sum = Range(1, 5).Aggregate(0, (acc, i) => acc + i);
15 | // => 15
16 |
17 | var count = Range(1, 5).Aggregate(0, (acc, _) => acc + 1);
18 | // => 5
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/Commands.cs:
--------------------------------------------------------------------------------
1 | namespace Boc.Commands
2 | {
3 | public class BookTransfer : MakeTransfer { }
4 | }
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/EitherBasedAPIs/BookTransferController.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using Microsoft.AspNetCore.Mvc;
4 | using System;
5 | using Unit = System.ValueTuple;
6 |
7 | namespace Boc.Api
8 | {
9 | public class Chapter6_BookTransferController : Controller
10 | {
11 | [HttpPost, Route("api/Chapters6/transfers/future/restful")]
12 | public IActionResult BookTransfer_v1([FromBody] BookTransfer request)
13 | => Handle(request).Match(
14 | Right: _ => Ok(),
15 | Left: BadRequest);
16 |
17 | [HttpPost, Route("api/Chapters6/transfers/future/resultDto")]
18 | public ResultDto BookTransfer_v2([FromBody] BookTransfer request)
19 | => Handle(request).ToResult();
20 |
21 | Either Handle(BookTransfer request) { throw new NotImplementedException(); }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/EitherBasedAPIs/InstrumentsController.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace Boc.Api
5 | {
6 | public class Chapter6_InstrumentsController : Controller
7 | {
8 | private IInstrumentService instruments;
9 |
10 | [HttpGet, Route("api/instruments/{ticker}/details")]
11 | public IActionResult GetAccountDetails(string ticker)
12 | => instruments.GetInstrumentDetails(ticker).Match(
13 | Some: Ok,
14 | None: NotFound);
15 | }
16 |
17 | public interface IInstrumentService
18 | {
19 | Option GetInstrumentDetails(string ticker);
20 | }
21 |
22 | public class InstrumentDetails { }
23 | }
24 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/EitherBasedAPIs/Result.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 |
3 | namespace Boc.Api
4 | {
5 | public class ResultDto
6 | {
7 | public bool Succeeded { get; }
8 | public bool Failed => !Succeeded;
9 |
10 | public T Data { get; }
11 | public Error Error { get; }
12 |
13 | internal ResultDto(T data) { Succeeded = true; Data = data; }
14 | internal ResultDto(Error error) { Error = error; }
15 | }
16 |
17 | public static class EitherExt
18 | {
19 | public static ResultDto ToResult(this Either @this)
20 | => @this.Match(
21 | Right: data => new ResultDto(data),
22 | Left: error => new ResultDto(error));
23 | }
24 | }
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/Errors.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 |
4 | namespace Boc.Domain
5 | {
6 | public static class Errors
7 | {
8 | public static InsufficientBalanceError InsufficientBalance
9 | => new InsufficientBalanceError();
10 |
11 | public static InvalidBicError InvalidBic
12 | => new InvalidBicError();
13 |
14 | public static CannotActivateClosedAccountError CannotActivateClosedAccount
15 | => new CannotActivateClosedAccountError();
16 |
17 | public static TransferDateIsPastError TransferDateIsPast
18 | => new TransferDateIsPastError();
19 |
20 | public static AccountNotActiveError AccountNotActive
21 | => new AccountNotActiveError();
22 |
23 | public static UnexpectedError UnexpectedError
24 | => new UnexpectedError();
25 |
26 | public static Error UnknownAccountId(Guid id)
27 | => new UnknownAccountId(id);
28 | }
29 |
30 | public sealed class UnknownAccountId : Error
31 | {
32 | Guid Id { get; }
33 | public UnknownAccountId(Guid id) { Id = id; }
34 |
35 | public override string Message
36 | => $"No account with id {Id} was found";
37 | }
38 |
39 | public sealed class UnexpectedError : Error
40 | {
41 | public override string Message { get; }
42 | = "An unexpected error has occurred";
43 | }
44 |
45 | public sealed class AccountNotActiveError : Error
46 | {
47 | public override string Message { get; }
48 | = "The account is not active; the requested operation cannot be completed";
49 | }
50 |
51 | public sealed class InvalidBicError : Error
52 | {
53 | public override string Message { get; }
54 | = "The beneficiary's BIC/SWIFT code is invalid";
55 | }
56 |
57 | public sealed class InsufficientBalanceError : Error
58 | {
59 | public override string Message { get; }
60 | = "Insufficient funds to fulfil the requested operation";
61 | }
62 |
63 | public sealed class CannotActivateClosedAccountError : Error
64 | {
65 | public override string Message { get; }
66 | = "Cannot activate an account that has been closed";
67 | }
68 |
69 | public sealed class TransferDateIsPastError : Error
70 | {
71 | public override string Message { get; }
72 | = "Transfer date cannot be in the past";
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/Particularized/BookTransferController.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using Boc.Domain;
3 | using Dapper;
4 | using Microsoft.AspNetCore.Mvc;
5 | using Microsoft.Extensions.Logging;
6 | using LaYumba.Functional;
7 | using static LaYumba.Functional.F;
8 | using Unit = System.ValueTuple;
9 | using System.Text.RegularExpressions;
10 | using System;
11 | using Examples.Chapter1.DbLogger;
12 |
13 | namespace Boc.ValidImpl
14 | {
15 | public class Chapter6_BookTransferController_WithValidation : Controller
16 | {
17 | ILogger logger;
18 |
19 | [HttpPost, Route("api/Chapters6/transfers/future/particularized")]
20 | public IActionResult MakeFutureTransfer([FromBody] BookTransfer request)
21 | => Handle(request).Match(
22 | Invalid: BadRequest,
23 | Valid: result => result.Match(
24 | Exception: OnFaulted,
25 | Success: _ => Ok()));
26 |
27 | IActionResult OnFaulted(Exception ex)
28 | {
29 | logger.LogError(ex.Message);
30 | return StatusCode(500, Errors.UnexpectedError);
31 | }
32 |
33 | Validation> Handle(BookTransfer request)
34 | => Validate(request)
35 | .Map(Save);
36 |
37 | Validation Validate(BookTransfer cmd)
38 | => ValidateBic(cmd).Bind(ValidateDate);
39 |
40 |
41 | // bic code validation
42 |
43 | static readonly Regex regex = new Regex("^[A-Z]{6}[A-Z1-9]{5}$");
44 |
45 | Validation ValidateBic(BookTransfer cmd)
46 | {
47 | if (!regex.IsMatch(cmd.Bic.ToUpper()))
48 | return Errors.InvalidBic;
49 | return cmd;
50 | }
51 |
52 | // date validation
53 |
54 | DateTime now;
55 |
56 | Validation ValidateDate(BookTransfer cmd)
57 | {
58 | if (cmd.Date.Date <= now.Date)
59 | return Errors.TransferDateIsPast;
60 | return cmd;
61 | }
62 |
63 | // persistence
64 |
65 | string connString;
66 |
67 | Exceptional Save(BookTransfer transfer)
68 | {
69 | try
70 | {
71 | ConnectionHelper.Connect(connString
72 | , c => c.Execute("INSERT ...", transfer));
73 | }
74 | catch (Exception ex) { return ex; }
75 | return Unit();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/TransferOnHandler/v1 Skeleton.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using Microsoft.AspNetCore.Mvc;
4 | using System;
5 | using Unit = System.ValueTuple;
6 |
7 | namespace Boc.EitherImpl.Services.Skeleton
8 | {
9 | class BookTransferController_Skeleton : Controller
10 | {
11 | [HttpPost, Route("transfers/book/skeleton")]
12 | public void BookTransfer([FromBody] BookTransfer request)
13 | => Handle(request);
14 |
15 | Either Handle(BookTransfer request)
16 | => Validate(request)
17 | .Bind(Save);
18 |
19 | Either Validate(BookTransfer request)
20 | { throw new NotImplementedException(); }
21 |
22 | Either Save(BookTransfer request)
23 | { throw new NotImplementedException(); }
24 | }
25 | }
--------------------------------------------------------------------------------
/Examples/Chapter06/Boc/TransferOnHandler/v2 ValidationMethods.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using Microsoft.AspNetCore.Mvc;
4 | using System;
5 | using System.Text.RegularExpressions;
6 | using Unit = System.ValueTuple;
7 |
8 | namespace Boc.EitherImpl.Services.ValidationMethods
9 | {
10 | using Domain;
11 | using static F;
12 |
13 | class BookTransferController_Skeleton : Controller
14 | {
15 | DateTime now;
16 | Regex bicRegex = new Regex("[A-Z]{11}");
17 |
18 | Either Handle(BookTransfer request)
19 | => Right(request)
20 | .Bind(ValidateBic)
21 | .Bind(ValidateDate)
22 | .Bind(Save);
23 |
24 | Either ValidateBic(BookTransfer request)
25 | {
26 | if (!bicRegex.IsMatch(request.Bic))
27 | return Errors.InvalidBic;
28 | else return request;
29 | }
30 |
31 | Either ValidateDate(BookTransfer request)
32 | {
33 | if (request.Date.Date <= now.Date)
34 | return Errors.TransferDateIsPast;
35 | else return request;
36 | }
37 |
38 | Either Save(BookTransfer request)
39 | { throw new NotImplementedException(); }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Examples/Chapter06/CookFavouriteFood.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using System;
3 | using Unit = System.ValueTuple;
4 |
5 | namespace Examples.Chapter5
6 | {
7 | class CookFavouriteDish
8 | {
9 | Func> WakeUpEarly;
10 | Func> ShopForIngredients;
11 | Func> CookRecipe;
12 |
13 | Action EnjoyTogether;
14 | Action ComplainAbout;
15 | Action OrderPizza;
16 |
17 | void Start()
18 | {
19 | WakeUpEarly()
20 | .Bind(ShopForIngredients)
21 | .Bind(CookRecipe)
22 | .Match(
23 | Right: dish => EnjoyTogether(dish),
24 | Left: reason =>
25 | {
26 | ComplainAbout(reason);
27 | OrderPizza();
28 | });
29 | }
30 | }
31 |
32 | class Reason { }
33 | class Ingredients { }
34 | class Food { }
35 | }
--------------------------------------------------------------------------------
/Examples/Chapter06/SimpleUsage.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using static LaYumba.Functional.F;
3 | using NUnit.Framework;
4 | using static System.Math;
5 |
6 | partial class Either_Example
7 | {
8 | Either Calc(double x, double y)
9 | {
10 | if (y == 0)
11 | return "y cannot be 0";
12 |
13 | if (x != 0 && Sign(x) != Sign(y))
14 | return "x / y cannot be negative";
15 |
16 | return Sqrt(x / y);
17 | }
18 |
19 | void UseMatch(double x, double y)
20 | {
21 | var message = Calc(x, y).Match(
22 | Right: z => $"Result: {z}",
23 | Left: err => $"Invalid input: {err}");
24 | }
25 |
26 | Either Run(double x, double y)
27 | => Calc(x, y)
28 | .Map(
29 | left: msg => Error(msg),
30 | right: d => d)
31 | .Bind(ToIntIfWhole);
32 |
33 | Either ToIntIfWhole(double d)
34 | {
35 | if ((int)d == d) return (int)d;
36 | return Error($"Expected a whole number but got {d}");
37 | }
38 | }
39 |
40 |
41 | partial class Either_Example
42 | {
43 | [TestCase(1d, 0d, ExpectedResult = false, TestName = "When y is 0 Calc fails")]
44 | [TestCase(-90d, 10d, ExpectedResult = false, TestName = "When x/y < 0 Calc fails")]
45 | [TestCase(90d, 10d, ExpectedResult = true, TestName = "Otherwise Calc succeeds")]
46 | public bool TestCalc(double x, double y) => Calc(x, y).Match(_ => false, _ => true);
47 | }
48 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Unbiased.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using System;
3 |
4 | namespace Examples.Chapter5.Either.Unbiased
5 | {
6 | interface IResearch
7 | {
8 | Either GetStudyMaterial(string topic);
9 | }
10 |
11 | class Unbiased_Example
12 | {
13 | IResearch research;
14 | Func Watch;
15 | Func Read;
16 |
17 | void Start()
18 | => research
19 | .GetStudyMaterial("Functional programming")
20 | .Match(
21 | Left: Watch,
22 | Right: Read)
23 | .PutInPractice();
24 | }
25 |
26 | class Knowledge
27 | {
28 | internal void PutInPractice()
29 | {
30 | throw new NotImplementedException();
31 | }
32 | }
33 | class Book
34 | {
35 | private string v;
36 |
37 | public Book(string v)
38 | {
39 | this.v = v;
40 | }
41 | }
42 | class Mooc
43 | {
44 | private string v;
45 |
46 | public Mooc(string v)
47 | {
48 | this.v = v;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Examples/Chapter06/Where.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using static LaYumba.Functional.F;
3 | using System;
4 |
5 | class Interview_Example_Option
6 | {
7 | Func IsEligible;
8 | Func> Interview;
9 |
10 | Option FirstRound(Candidate c)
11 | => Some(c)
12 | .Where(IsEligible)
13 | .Bind(Interview);
14 | }
15 |
16 | class Interview_Example_Either
17 | {
18 | Func IsEligible;
19 | Func> Interview;
20 |
21 | Either CheckEligibility(Candidate c)
22 | {
23 | if (IsEligible(c)) return c;
24 | return new Rejection("Not eligible");
25 | }
26 |
27 | Either FirstRound(Candidate c)
28 | => Right(c)
29 | .Bind(CheckEligibility)
30 | .Bind(Interview);
31 | }
32 |
33 | class Candidate { }
34 | class Rejection
35 | {
36 | private string reason;
37 |
38 | public Rejection(string reason)
39 | {
40 | this.reason = reason;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/FP/Sql.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using Boc.Commands;
4 | using Unit = System.ValueTuple;
5 |
6 | namespace Boc.Chapter7
7 | {
8 | using static F;
9 | using Examples;
10 |
11 | namespace Delegate
12 | {
13 | public static class Sql
14 | {
15 | public static class Queries
16 | {
17 | public static readonly SqlTemplate InsertTransferOn = "INSERT ...";
18 | }
19 |
20 | public static Func< ConnectionString
21 | , SqlTemplate
22 | , object
23 | , Exceptional >
24 | TryExecute => (conn, sql, t) =>
25 | {
26 | try { conn.Execute(sql, t); }
27 | catch (Exception ex) { return ex; }
28 | return Unit();
29 | };
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/OOP/Controller.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using Boc.Domain;
3 | using Microsoft.AspNetCore.Mvc;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 | using LaYumba.Functional;
7 | using Unit = System.ValueTuple;
8 |
9 | namespace Boc.Chapter7
10 | {
11 | // option 1. use MVC, and inject an IValidator and IRepository as dependencies
12 | namespace OOP
13 | {
14 | public interface IValidator
15 | {
16 | Validation Validate(T request);
17 | }
18 |
19 | public interface IRepository
20 | {
21 | Option Lookup(Guid id);
22 | Exceptional Save(T entity);
23 | }
24 |
25 | public class BookTransferController : Controller
26 | {
27 | IValidator validator;
28 | IRepository repository;
29 |
30 | public BookTransferController
31 | (IValidator validator, IRepository repository)
32 | {
33 | this.validator = validator;
34 | this.repository = repository;
35 | }
36 |
37 | //[HttpPost, Route("api/Chapters7/transfers/future")]
38 | public IActionResult BookTransfer([FromBody] BookTransfer cmd)
39 | => validator.Validate(cmd)
40 | .Map(repository.Save)
41 | .Match(
42 | Invalid: BadRequest,
43 | Valid: result => result.Match(
44 | Exception: _ => StatusCode(500, Errors.UnexpectedError),
45 | Success: _ => Ok()));
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/OOP/Repository.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using Boc.Commands;
4 | using Unit = System.ValueTuple;
5 |
6 | namespace Boc.Chapter7.OOP
7 | {
8 | using static F;
9 | using Examples;
10 |
11 | public class BookTransferRepository : IRepository
12 | {
13 | ConnectionString conn;
14 |
15 | public BookTransferRepository(ConnectionString conn)
16 | {
17 | this.conn = conn;
18 | }
19 |
20 | public Option Lookup(Guid id)
21 | { throw new NotImplementedException("Illustrates violating interface segregation"); }
22 |
23 | public Exceptional Save(BookTransfer transfer)
24 | {
25 | try { conn.Execute("INSERT ...", transfer); }
26 | catch (Exception ex) { return ex; }
27 | return Unit();
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/OOP/Validators/BicCodeValidator.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using System.Text.RegularExpressions;
4 | using Boc.Domain;
5 |
6 | namespace Boc.Chapter7.OOP
7 | {
8 | public class BicCodeValidator : IValidator
9 | {
10 | static readonly Regex regex = new Regex("^[A-Z]{6}[A-Z1-9]{5}$");
11 |
12 | public Validation Validate(MakeTransfer request)
13 | {
14 | if (!regex.IsMatch(request.Bic.ToUpper()))
15 | return Errors.InvalidBic;
16 | return request;
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/OOP/Validators/DateNotPastValidator.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using Boc.Domain;
4 | using Boc.Services.Validation.WithDI;
5 |
6 | namespace Boc.Chapter7.OOP
7 | {
8 | public class DateNotPastValidator : IValidator
9 | {
10 | private readonly IDateTimeService clock;
11 |
12 | public DateNotPastValidator(IDateTimeService clock)
13 | {
14 | this.clock = clock;
15 | }
16 |
17 | public Validation Validate(MakeTransfer request)
18 | {
19 | if (request.Date.Date <= clock.UtcNow.Date)
20 | return Errors.TransferDateIsPast;
21 | return request;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/Examples/Chapter07/Boc/OOP/Validators/IbanValidator.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using LaYumba.Functional;
3 | using System.Text.RegularExpressions;
4 | using Boc.Domain;
5 |
6 | namespace Boc.Chapter7.OOP
7 | {
8 | public class IbanValidator : IValidator
9 | {
10 | private const string ERROR_MESSAGE = "The beneficiary's IBAN code is invalid";
11 | private readonly Regex regex = new Regex(
12 | "[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}");
13 |
14 | public Validation Validate(MakeTransfer request)
15 | {
16 | if (regex.IsMatch(request.Iban))
17 | return Errors.InvalidBic;
18 | return request;
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Examples/Chapter07/ConnectionStringExt.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Dapper;
4 | using Examples.Chapter1.DbLogger;
5 |
6 | namespace Examples
7 | {
8 | using static ConnectionHelper;
9 |
10 | public static class ConnectionStringExt
11 | {
12 | public static Func>
13 | Query(this ConnectionString connString)
14 | => (sql, param)
15 | => Connect(connString, conn => conn.Query(sql, param));
16 |
17 | public static void Execute(this ConnectionString connString
18 | , SqlTemplate sql, object param)
19 | => Connect(connString, conn => conn.Execute(sql, param));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/Chapter07/DbQueries/ConnectionString.cs:
--------------------------------------------------------------------------------
1 | namespace Examples
2 | {
3 | public class ConnectionString
4 | {
5 | string Value { get; }
6 | public ConnectionString(string value) { Value = value; }
7 |
8 | public static implicit operator string(ConnectionString c)
9 | => c.Value;
10 | public static implicit operator ConnectionString(string s)
11 | => new ConnectionString(s);
12 |
13 | public override string ToString() => Value;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/Chapter07/DbQueries/EmployeeLookup.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace Examples.Chapter7
7 | {
8 | public static class EmployeeLookup
9 | {
10 | public static void Run()
11 | {
12 | ConnectionString conn = "my-database";
13 |
14 | SqlTemplate select = "SELECT * FROM EMPLOYEES"
15 | , sqlById = $"{select} WHERE ID = @Id"
16 | , sqlByName = $"{select} WHERE LASTNAME = @LastName";
17 |
18 | // queryEmployees : (SqlTemplate, object) → IEnumerable
19 | var queryEmployees = conn.Query();
20 |
21 | // queryById : object → IEnumerable
22 | var queryById = queryEmployees.Apply(sqlById);
23 |
24 | // queryByLastName : object → IEnumerable
25 | var queryByLastName = queryEmployees.Apply(sqlByName);
26 |
27 | // LookupEmployee : Guid → Option
28 | Option LookupEmployee(Guid id)
29 | => queryById(new { Id = id }).FirstOrDefault();
30 |
31 | // FindEmployeesByLastName : string → IEnumerable
32 | IEnumerable FindEmployeesByLastName(string lastName)
33 | => queryByLastName(new { LastName = lastName });
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Examples/Chapter07/DbQueries/SqlTemplate.cs:
--------------------------------------------------------------------------------
1 | namespace Examples
2 | {
3 | public class SqlTemplate
4 | {
5 | string Value { get; }
6 | public SqlTemplate(string value) { Value = value; }
7 |
8 | public static implicit operator string(SqlTemplate c)
9 | => c.Value;
10 | public static implicit operator SqlTemplate(string s)
11 | => new SqlTemplate(s);
12 |
13 | public override string ToString() => Value;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/Chapter07/Greetings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 |
4 | using Name = System.String;
5 | using Greeting = System.String;
6 | using PersonalizedGreeting = System.String;
7 |
8 | namespace Examples.Chapter7
9 | {
10 | using static Console;
11 |
12 | public static class Greetings
13 | {
14 | internal static void Run()
15 | {
16 | Func greet
17 | = (gr, name) => $"{gr}, {name}";
18 |
19 | Func> greetWith
20 | = gr => name => $"{gr}, {name}";
21 |
22 | var names = new Name[] { "Tristan", "Ivan" };
23 |
24 | WriteLine("Greet - with 'normal', multi-argument application");
25 | names.Map(g => greet("Hello", g)).ForEach(WriteLine);
26 | // prints: Hello, Tristan
27 | // Hello, Ivan
28 |
29 | WriteLine("Greet formally - with partial application, manual");
30 | var greetFormally = greetWith("Good evening");
31 | names.Map(greetFormally).ForEach(WriteLine);
32 | // prints: Good evening, Tristan
33 | // Good evening, Ivan
34 |
35 | WriteLine("Greet informally - with partial application, general");
36 | var greetInformally = greet.Apply("Hey");
37 | names.Map(greetInformally).ForEach(WriteLine);
38 | // prints: Hey, Tristan
39 | // Hey, Ivan
40 |
41 | WriteLine("Greet nostalgically - with partial application, currying");
42 | var greetNostalgically = greet.Curry()("Arrivederci");
43 | names.Map(greetNostalgically).ForEach(WriteLine);
44 | // prints: Arrivederci, Tristan
45 | // Arrivederci, Ivan
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Examples/Chapter07/TypeInference.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using static LaYumba.Functional.F;
4 |
5 | using Name = System.String;
6 | using Greeting = System.String;
7 | using PersonalizedGreeting = System.String;
8 |
9 | namespace Examples.Chapter7
10 | {
11 | public class TypeInference_Method
12 | {
13 | // 1. method
14 | PersonalizedGreeting GreeterMethod(Greeting gr, Name name)
15 | => $"{gr}, {name}";
16 |
17 | // the below does NOT compile!
18 | //Func __GreetWith(Greeting greeting)
19 | // => GreeterMethod.Apply(greeting);
20 |
21 | // the lines below compiles, but oh my!
22 | Func GreetWith_1(Greeting greeting)
23 | => FuncExt.Apply(GreeterMethod, greeting);
24 |
25 | Func _GreetWith_2(Greeting greeting)
26 | => new Func(GreeterMethod)
27 | .Apply(greeting);
28 | }
29 |
30 | public class TypeInference_Delegate
31 | {
32 | string separator = "! ";
33 |
34 | // 1. field
35 | Func GreeterField
36 | = (gr, name) => $"{gr}, {name}";
37 |
38 | // 2. property
39 | Func GreeterProperty
40 | => (gr, name) => $"{gr}{separator}{name}";
41 |
42 | // 3. factory
43 | Func GreeterFactory()
44 | => (gr, t) => $"{gr}, {t}";
45 |
46 | Func CreateGreetingWith(Greeting greeting)
47 | {
48 | // 1. field
49 | return GreeterField.Apply(greeting);
50 |
51 | // 2. property
52 | return GreeterProperty.Apply(greeting);
53 |
54 | // 3. factory
55 | return GreeterFactory().Apply(greeting);
56 | }
57 |
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Examples/Chapter08/LINQ/EnumerableExamples.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using LaYumba.Functional;
4 |
5 | namespace Examples.Chapter8.Linq
6 | {
7 | using static Console;
8 | using static F;
9 |
10 | class EnumerableExamples
11 | {
12 | internal static void _main()
13 | {
14 | SimpleSelectExample();
15 | WriteLine();
16 | SimpleSelectManyExample();
17 |
18 | ReadKey();
19 | }
20 |
21 | public static void SimpleSelectExample()
22 | {
23 | var a =
24 | from x in Range(1, 4)
25 | select x * 2;
26 |
27 | var b =
28 | Range(1, 4)
29 | .Map(x => x * 2);
30 |
31 | a.ForEach(WriteLine);
32 | WriteLine();
33 | b.ForEach(WriteLine);
34 | WriteLine();
35 | }
36 |
37 | public static void SimpleSelectManyExample()
38 | {
39 | var a =
40 | from c in Range('a', 'c')
41 | from i in Range(2, 3)
42 | select (c, i);
43 |
44 | var b =
45 | Range('a', 'c')
46 | .SelectMany(c => Range(2, 3)
47 | .Select(i => (c, i)));
48 |
49 | var d = Range('a', 'c')
50 | .SelectMany(c => Range(2, 3)
51 | , (c, i) => (c, i));
52 |
53 |
54 |
55 | a.ForEach(t => WriteLine($"({t.Item1}, {t.Item2})"));
56 | WriteLine();
57 | b.ForEach(t => WriteLine($"({t.Item1}, {t.Item2})"));
58 | WriteLine();
59 | d.ForEach(t => WriteLine($"({t.Item1}, {t.Item2})"));
60 | WriteLine();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Examples/Chapter08/MapBinaryFuncExample.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using static LaYumba.Functional.F;
4 | using static System.Console;
5 | using System.Collections.Generic;
6 |
7 | namespace Examples.Chapter7.Apply
8 | {
9 | public static class MapBinaryFuncExample
10 | {
11 | static Func multiply = (i, j) => i * j;
12 | static Func @double = i => i * 2;
13 |
14 | internal static void MapUnaryFunction()
15 | {
16 | Option double3 = Some(3).Map(@double);
17 | // => Some(6)
18 |
19 | IEnumerable doubles = Range(1, 3).Map(@double);
20 | // => [2, 4, 6]
21 | }
22 |
23 | internal static void ApplyBinaryFunctionByLowering()
24 | {
25 | Option a = Some(3);
26 | Option b = Some(4);
27 |
28 | var result = a.Match(
29 | () => None,
30 | valA => b.Match(
31 | () => None,
32 | valB => Some(multiply(valA, valB))
33 | )
34 | );
35 | // => Some(12)
36 |
37 | Some(3).Map(multiply.Curry());
38 | }
39 |
40 | internal static void _main()
41 | {
42 | Option> multiplyBy3 = Some(3).Map(multiply);
43 | // => Some(x => multiply(3, x))
44 | IEnumerable> multiplers = Range(1, 3).Map(multiply);
45 | // => [x => multiply(1, x), x => multiply(2, x), x => multiply(3, x)]
46 |
47 | //multiplers.Map(f => f(2)).ForEach(Out.WriteLine);
48 | Some(3).Map(multiply).Apply(Some(2));
49 | }
50 |
51 | static Option MultiplicationWithBind(string strX, string strY)
52 | => Int.Parse(strX).Bind(x => Int.Parse(strY).Bind(y => multiply(x, y)));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Examples/Chapter08/Traversables.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using System;
3 | using System.Linq;
4 | using NUnit.Framework;
5 | using Double = LaYumba.Functional.Double;
6 | using String = LaYumba.Functional.String;
7 |
8 | namespace Examples.Chapter8
9 | {
10 | using static F;
11 |
12 | public class Traversable_Validation
13 | {
14 | public static void _main()
15 | {
16 | var input = Console.ReadLine();
17 | var result = Process(input);
18 | Console.WriteLine(result);
19 | }
20 |
21 | static Validation Validate(string s)
22 | => Double.Parse(s).Match(
23 | () => Error($"'{s}' is not a valid number"),
24 | d => Valid(d));
25 |
26 | static string Process(string input)
27 | => input.Split(',') // Array
28 | .Map(String.Trim) // IEnumerable
29 | .Traverse(Validate) // Validation>
30 | .Map(Enumerable.Sum) // Validation
31 | .Match(
32 | Invalid: errs => string.Join(", ", errs),
33 | Valid: sum => $"The sum is {sum}");
34 |
35 | [TestCase("1, 2, 3", ExpectedResult = "The sum is 6")]
36 | [TestCase("one, two, 3", ExpectedResult = "'one' is not a valid number, 'two' is not a valid number")]
37 | public string TraversableIEnumerableValidation(string s) => Process(s);
38 | }
39 |
40 | public class Traversable_Option
41 | {
42 | public static void _main()
43 | {
44 | var input = Console.ReadLine();
45 | var result = Process(input);
46 | Console.WriteLine(result);
47 | }
48 |
49 | static string Process(string input)
50 | => input.Split(',') // IEnumerable
51 | .Map(String.Trim) // IEnumerable
52 | .Traverse(Double.Parse) // Option>
53 | .Map(Enumerable.Sum) // Option
54 | .Match(
55 | () => "Some of your inputs could not be parsed",
56 | sum => $"The sum is {sum}");
57 |
58 | [TestCase("1, 2, 3", ExpectedResult = "The sum is 6")]
59 | [TestCase("one, two, 3", ExpectedResult = "Some of your inputs could not be parsed")]
60 | public string TraversableIEnumerableOption(string s) => Process(s);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Examples/Chapter08/TypeInference_Map.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional;
3 | using static LaYumba.Functional.F;
4 |
5 | namespace Examples.Chapter7
6 | {
7 | class TypeInference_Map
8 | {
9 | // 1. method
10 | int AddMethod(int x, int y) => x + y;
11 |
12 | // 2. field
13 | Func AddField = (x, y) => x + y;
14 |
15 | // 3. factory
16 | Func AddFactory() => (x, y) => x + y;
17 |
18 | public void Demonstrate()
19 | {
20 | // the line below does NOT compile
21 | //Some(3).Map(AddMethod).Apply(Some(4));
22 |
23 | Some(3).Map(AddMethod).Apply(Some(4));
24 | Some(3).Map(AddField).Apply(Some(4));
25 | Some(3).Map(AddFactory()).Apply(Some(4));
26 | }
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Boc/Account_Mutable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Boc.Domain;
3 |
4 | namespace Examples.Chapter10.Data.Account.Mutable
5 | {
6 | public class AccountState
7 | {
8 | public AccountStatus Status { get; set; }
9 | public CurrencyCode Currency { get; set; }
10 | public decimal AllowedOverdraft { get; set; }
11 | public List TransactionHistory { get; set; }
12 |
13 | public AccountState()
14 | {
15 | TransactionHistory = new List();
16 | }
17 | public AccountState WithStatus(AccountStatus newStatus)
18 | => new AccountState
19 | {
20 | Status = newStatus,
21 | Currency = this.Currency,
22 | AllowedOverdraft = this.AllowedOverdraft,
23 | TransactionHistory = this.TransactionHistory
24 | };
25 | }
26 |
27 | class Usage
28 | {
29 | void _main()
30 | {
31 | var account = new AccountState
32 | {
33 | Status = AccountStatus.Active
34 | };
35 | var newState = account.WithStatus(AccountStatus.Frozen);
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Boc/CurrencyCode.cs:
--------------------------------------------------------------------------------
1 | namespace Boc.Domain
2 | {
3 | public struct CurrencyCode
4 | {
5 | string Value { get; }
6 | public CurrencyCode(string value) { Value = value; }
7 |
8 | public static implicit operator string(CurrencyCode c)
9 | => c.Value;
10 | public static implicit operator CurrencyCode(string s)
11 | => new CurrencyCode(s);
12 |
13 | public override string ToString() => Value;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Boc/Transaction.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter10.Data.Account
4 | {
5 | public class Transaction
6 | {
7 | public decimal Amount { get; }
8 | public string Description { get; }
9 | public DateTime Date { get; }
10 | }
11 | }
--------------------------------------------------------------------------------
/Examples/Chapter09/Boc/WithFSharp.cs:
--------------------------------------------------------------------------------
1 |
2 |
3 | //F# interop currently not supported by VS when using .NET Core,
4 | //but compiles using the CLI
5 | //http://stackoverflow.com/questions/37719061/c-sharp-f-interop-support-in-visual-studio-2015-on-net-core
6 |
7 | //using Boc.Domain.FSharp;
8 | //using AccountStatus = Boc.Domain.FSharp.AccountStatus;
9 |
10 | //namespace Examples.Chapter10.Data.Account
11 | //{
12 | // public static class WithFSharp
13 | // {
14 | // static AccountState Activate(this AccountState acc)
15 | // => acc.WithStatus(AccountStatus.Active);
16 | // }
17 | //}
18 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Circle.cs:
--------------------------------------------------------------------------------
1 | using static System.Math;
2 |
3 | namespace Examples.Chapter10.Data
4 | {
5 | struct Circle
6 | {
7 | public Circle(Point center, double radius)
8 | {
9 | Center = center;
10 | Radius = radius;
11 | }
12 |
13 | public Point Center { get; }
14 | public double Radius { get; }
15 |
16 | public double Area => PI * Pow(Radius, 2);
17 | }
18 |
19 | struct Point
20 | {
21 | public double X { get; }
22 | public double Y { get; }
23 | public Point(double x, double y) { X = x; Y = y; }
24 | }
25 |
26 | static class CircleExt
27 | {
28 | static Circle Scale(this Circle @this, double factor)
29 | => new Circle(@this.Center, @this.Radius * factor);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Coyo.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional;
2 | using LaYumba.Functional.Data.LinkedList;
3 | using System;
4 |
5 | using String = LaYumba.Functional.String;
6 | using static LaYumba.Functional.Data.LinkedList.LinkedList;
7 |
8 | namespace Examples.Chapter10.Data
9 | {
10 | public class CoyoExample
11 | {
12 | public static void _main()
13 | {
14 | var emails = List(" Some@Ema.il ");
15 |
16 | // eager evaluation: 2 passes through the list
17 | emails.Map(String.Trim)
18 | .Map(String.ToLower)
19 | .ForEach(Console.WriteLine);
20 |
21 | // lazy evaluation: coyo just composes the functions to be mapped
22 | var coyo = Coyo.Of, string>(emails)
23 | .Map(String.Trim)
24 | .Map(String.ToLower);
25 |
26 | // composed functions are applied only when Run is called
27 | // with a single pass over the list
28 | var results = coyo.Run();
29 | results.ForEach(Console.WriteLine);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Examples/Chapter09/DateTime.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter0.Introduction
4 | {
5 | using static Console;
6 |
7 | class DateTime_Example
8 | {
9 | internal static void _main()
10 | {
11 | var momsBirthday = new DateTime(1966, 12, 13);
12 | momsBirthday.AddDays(-7);
13 | WriteLine(momsBirthday);
14 |
15 | var johnsBirthday = momsBirthday;
16 | johnsBirthday = johnsBirthday.AddDays(1);
17 | WriteLine("Johns: " + johnsBirthday.Date);
18 | WriteLine("moms: " + momsBirthday.Date);
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Examples/Chapter09/ImmutableCollections.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 |
5 | namespace Examples.Chapter0.Introduction
6 | {
7 | public class Order_MutableList
8 | {
9 | public Guid CustomerId { get; }
10 | public DateTime CreatedAt { get; }
11 | public List OrderLines { get; }
12 |
13 | public Order_MutableList(Guid customerId, DateTime createdAt
14 | , List orderLines)
15 | {
16 | this.CustomerId = customerId;
17 | this.CreatedAt = createdAt;
18 | this.OrderLines = orderLines;
19 | }
20 | }
21 |
22 | public class Order
23 | {
24 | public Guid CustomerId { get; }
25 | public DateTime CreatedAt { get; }
26 | public ImmutableList OrderLines { get; }
27 |
28 | public Order(Guid customerId, DateTime createdAt
29 | , IEnumerable orderLines)
30 | {
31 | CustomerId = customerId;
32 | CreatedAt = createdAt;
33 | OrderLines = orderLines.ToImmutableList();
34 | }
35 |
36 | public Order AddOrderLine(OrderLine newLine)
37 | => new Order(CustomerId, CreatedAt, OrderLines.Add(newLine));
38 | }
39 |
40 | public class OrderLine
41 | {
42 | }
43 |
44 | class ImmutableCollections_Examples
45 | {
46 | public void ChangeMutableReadonlyList(Order order)
47 | => order.OrderLines.Clear();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Examples/Chapter09/Introductory.cs:
--------------------------------------------------------------------------------
1 | using static System.Linq.Enumerable;
2 | using static System.Console;
3 | using System.Threading.Tasks;
4 | using System;
5 |
6 | namespace Examples.Chapter10.Data
7 | {
8 | class Product
9 | {
10 | public int Inventory { get; private set; }
11 |
12 | public void ReplenishInventory(int units)
13 | => Inventory += units;
14 |
15 | public void ProcessSale(int units)
16 | => Inventory -= units;
17 | }
18 |
19 | class Product_
20 | {
21 | int inventory;
22 |
23 | public bool IsLowOnInventory { get; private set; }
24 | public int Inventory
25 | {
26 | get { return inventory; }
27 | private set
28 | {
29 | inventory = value;
30 |
31 | IsLowOnInventory = inventory <= 5;
32 | }
33 | }
34 | }
35 |
36 | public class LocalMutationIsOk
37 | {
38 | int Sum(int[] ints)
39 | {
40 | var result = 0;
41 | foreach (int i in ints) result += i;
42 | return result;
43 | }
44 |
45 | public static void _main()
46 | {
47 | var nums = Range(-10000, 20001).Reverse().ToList();
48 | Parallel.Invoke(
49 | () => WriteLine(nums.Sum()),
50 | () => { nums.Sort(); WriteLine(nums.Sum()); });
51 | }
52 |
53 | public static void __main()
54 | {
55 | var nums = Range(-10000, 20001).Reverse().ToList();
56 |
57 | Action task1 = () => WriteLine(nums.Sum());
58 | Action task2 = () => { nums.Sort(); WriteLine(nums.Sum()); };
59 |
60 | Parallel.Invoke(task1, task2);
61 | }
62 |
63 | public static void WithIEnumerable()
64 | {
65 | var nums = Range(-10000, 20001).Reverse();
66 |
67 | Action task1 = () => WriteLine(nums.Sum());
68 | Action task2 = () => { nums.OrderBy(x => x); WriteLine(nums.Sum()); };
69 |
70 | Parallel.Invoke(task1, task2);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Examples/Chapter09/LinkedList.cs:
--------------------------------------------------------------------------------
1 | using LaYumba.Functional.Data.LinkedList;
2 | using static LaYumba.Functional.Data.LinkedList.LinkedList;
3 | using static System.Console;
4 |
5 | namespace Examples.Chapter10
6 | {
7 | static class ImmutableList_Example
8 | {
9 | static void _main()
10 | {
11 | var fruit = List("pineapple", "banana");
12 | WriteLine(fruit);
13 | // => ["pineapple", "banana"]
14 |
15 | var tropicalMix = fruit.Add("kiwi");
16 | WriteLine(tropicalMix);
17 | // => ["kiwi", "pineapple", "banana"]
18 |
19 | var yellowFruit = fruit.Add("lemon");
20 | WriteLine(yellowFruit);
21 | // => ["lemon", "pineapple", "banana"]
22 |
23 | ReadKey();
24 | }
25 |
26 | public static int Sum(this List @this)
27 | => @this.Match(
28 | Empty: () => 0,
29 | Cons: (head, tail) => head + tail.Sum());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Examples/Chapter09/TreeMap.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using LaYumba.Functional.Data.BinaryTree;
3 |
4 | namespace Examples
5 | {
6 | using static Tree;
7 |
8 | public static class TreePatternMatching
9 | {
10 | internal static void _main()
11 | {
12 | var tree = Branch
13 | (
14 | Left: Leaf("one"),
15 | Right: Branch
16 | (
17 | Left: Leaf("two"),
18 | Right: Leaf("three")
19 | )
20 | );
21 |
22 | Console.WriteLine(tree.Map(s => s.ToUpper()));
23 | Console.ReadKey();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Examples/Chapter09/UnknownRewardTypeException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Examples.Chapter10.Data
4 | {
5 | internal class UnknownRewardTypeException : Exception
6 | {
7 | public UnknownRewardTypeException()
8 | {
9 | }
10 |
11 | public UnknownRewardTypeException(string message) : base(message)
12 | {
13 | }
14 |
15 | public UnknownRewardTypeException(string message, Exception innerException) : base(message, innerException)
16 | {
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/Examples/Chapter10/CommandExt.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using Boc.Domain.Events;
3 |
4 | namespace Boc.Chapter10
5 | {
6 | public static class CommandExt
7 | {
8 | public static DebitedTransfer ToEvent(this MakeTransfer cmd)
9 | => new DebitedTransfer
10 | {
11 | Beneficiary = cmd.Beneficiary,
12 | Bic = cmd.Bic,
13 | DebitedAmount = cmd.Amount,
14 | EntityId = cmd.DebitedAccountId,
15 | Iban = cmd.Iban,
16 | Reference = cmd.Reference,
17 | Timestamp = cmd.Timestamp
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/Chapter10/Commands.cs:
--------------------------------------------------------------------------------
1 | using Boc.Domain;
2 | using Boc.Domain.Events;
3 | using System;
4 |
5 | namespace Boc.Commands
6 | {
7 | public class CreateAccount : Command
8 | {
9 | public Guid AccountId { get; set; }
10 | public CurrencyCode Currency { get; set; }
11 |
12 | public CreatedAccount ToEvent() => new CreatedAccount
13 | {
14 | EntityId = this.AccountId,
15 | Timestamp = this.Timestamp,
16 | Currency = this.Currency,
17 | };
18 | }
19 |
20 | public class AcknowledgeCashDeposit : Command
21 | {
22 | public Guid AccountId { get; set; }
23 | public decimal Amount { get; set; }
24 | public Guid BranchId { get; set; }
25 |
26 | public DepositedCash ToEvent() => new DepositedCash
27 | {
28 | EntityId = this.AccountId,
29 | Timestamp = this.Timestamp,
30 | Amount = this.Amount,
31 | BranchId = this.BranchId,
32 | };
33 | }
34 |
35 | public class SetOverdraft : Command
36 | {
37 | public Guid AccountId { get; set; }
38 | public decimal Amount { get; set; }
39 |
40 | public AlteredOverdraft ToEvent(decimal by) => new AlteredOverdraft
41 | {
42 | EntityId = this.AccountId,
43 | Timestamp = this.Timestamp,
44 | By = by,
45 | };
46 | }
47 |
48 | public class FreezeAccount : Command
49 | {
50 | public Guid AccountId { get; set; }
51 |
52 | public FrozeAccount ToEvent() => new FrozeAccount
53 | {
54 | EntityId = this.AccountId,
55 | Timestamp = this.Timestamp,
56 | };
57 | }
58 | }
--------------------------------------------------------------------------------
/Examples/Chapter10/Data/IEventStore.cs:
--------------------------------------------------------------------------------
1 | using Boc.Domain.Events;
2 | using System;
3 | using System.Collections.Generic;
4 |
5 | namespace Boc.Data
6 | {
7 | public interface IEventStore
8 | {
9 | void Persist(Event e);
10 | void Persist(IEnumerable e);
11 | IEnumerable GetEvents(Guid id);
12 | }
13 | }
--------------------------------------------------------------------------------
/Examples/Chapter10/Data/InMemoryEventStore.cs:
--------------------------------------------------------------------------------
1 | using Boc.Domain.Events;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace Boc.Data
7 | {
8 | public class InMemoryEventStore : IEventStore
9 | {
10 | private readonly List _store = new List();
11 |
12 | public void Persist(Event e)
13 | {
14 | _store.Add(e);
15 | }
16 |
17 | public void Persist(IEnumerable e)
18 | {
19 | _store.AddRange(e);
20 | }
21 |
22 | public IEnumerable GetEvents(Guid id)
23 | {
24 | return from stored in _store
25 | where stored.EntityId.Equals(id)
26 | orderby stored.Timestamp
27 | select stored;
28 | }
29 |
30 | public int Count()
31 | {
32 | return _store.Count;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Examples/Chapter10/Domain/Account.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using Boc.Domain;
3 | using Boc.Domain.Events;
4 | using LaYumba.Functional;
5 | using static LaYumba.Functional.F;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 |
10 | namespace Boc.Chapter10.Domain
11 | {
12 | public static class Account
13 | {
14 | // handle commands
15 |
16 | public static Validation<(Event Event, AccountState NewState)> Debit
17 | (this AccountState @this, MakeTransfer cmd)
18 | {
19 | if (@this.Status != AccountStatus.Active)
20 | return Errors.AccountNotActive;
21 |
22 | if (@this.Balance - cmd.Amount < @this.AllowedOverdraft)
23 | return Errors.InsufficientBalance;
24 |
25 | var evt = cmd.ToEvent();
26 | var newState = @this.Apply(evt);
27 |
28 | return (evt as Event, newState);
29 | }
30 |
31 | public static Validation<(Event Event, AccountState NewState)> Freeze
32 | (this AccountState @this, FreezeAccount cmd)
33 | {
34 | if (@this.Status == AccountStatus.Frozen)
35 | return Errors.AccountNotActive;
36 |
37 | var evt = cmd.ToEvent();
38 | var newState = @this.Apply(evt);
39 |
40 | return (evt as Event, newState);
41 | }
42 |
43 | // apply events
44 |
45 | public static AccountState Create(CreatedAccount evt)
46 | => new AccountState
47 | (
48 | Currency: evt.Currency,
49 | Status: AccountStatus.Active
50 | );
51 |
52 | public static AccountState Apply(this AccountState @this, Event evt)
53 | => new Pattern
54 | {
55 | (DepositedCash e) => @this.Credit(e.Amount),
56 | (DebitedTransfer e) => @this.Debit(e.DebitedAmount),
57 | (FrozeAccount e) => @this.WithStatus(AccountStatus.Frozen),
58 | }
59 | .Match(evt);
60 |
61 | // hydrate
62 |
63 | public static Option From(IEnumerable history)
64 | => history.Match(
65 | Empty: () => None,
66 | Otherwise: (createdEvent, otherEvents) => Some(
67 | otherEvents.Aggregate(
68 | seed: Account.Create((CreatedAccount)createdEvent),
69 | func: (state, evt) => state.Apply(evt))));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Examples/Chapter10/Domain/AccountState.cs:
--------------------------------------------------------------------------------
1 | using Boc.Domain;
2 |
3 | namespace Boc.Domain
4 | {
5 | public enum AccountStatus
6 | { Requested, Active, Frozen, Dormant, Closed }
7 | }
8 |
9 | namespace Boc.Chapter10.Domain
10 | {
11 | public sealed class AccountState
12 | {
13 | public AccountStatus Status { get; }
14 | public CurrencyCode Currency { get; }
15 | public decimal Balance { get; }
16 | public decimal AllowedOverdraft { get; }
17 |
18 | public AccountState
19 | ( CurrencyCode Currency
20 | , AccountStatus Status = AccountStatus.Requested
21 | , decimal Balance = 0
22 | , decimal AllowedOverdraft = 0)
23 | {
24 | this.Currency = Currency;
25 | this.Status = Status;
26 | this.Balance = Balance;
27 | this.AllowedOverdraft = AllowedOverdraft;
28 | }
29 |
30 | public AccountState Debit(decimal amount) => Credit(-amount);
31 |
32 | public AccountState Credit(decimal amount)
33 | => new AccountState(
34 | Currency: this.Currency,
35 | Status: this.Status,
36 | Balance: this.Balance + amount,
37 | AllowedOverdraft: this.AllowedOverdraft
38 | );
39 |
40 | public AccountState WithStatus(AccountStatus newStatus)
41 | => new AccountState(
42 | Currency: this.Currency,
43 | Status: newStatus,
44 | Balance: this.Balance,
45 | AllowedOverdraft: this.AllowedOverdraft
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/Chapter10/Events.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Boc.Domain.Events
4 | {
5 | public class Event
6 | {
7 | public Guid EntityId { get; set; }
8 | public DateTime Timestamp { get; set; }
9 | }
10 |
11 | public class CreatedAccount : Event
12 | {
13 | public CurrencyCode Currency { get; set; }
14 | }
15 |
16 | public class AlteredOverdraft : Event
17 | {
18 | public decimal By { get; set; }
19 | }
20 |
21 | public class FrozeAccount : Event { }
22 |
23 | public class DepositedCash : Event
24 | {
25 | public decimal Amount { get; set; }
26 | public Guid BranchId { get; set; }
27 | }
28 |
29 | public class DebitedTransfer : Event
30 | {
31 | public string Beneficiary { get; set; }
32 | public string Iban { get; set; }
33 | public string Bic { get; set; }
34 |
35 | public decimal DebitedAmount { get; set; }
36 | public string Reference { get; set; }
37 | }
38 |
39 | class DebitedFee : Event
40 | {
41 | public decimal Amount { get; }
42 | public string Description { get; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Examples/Chapter10/Transitions/Account.cs:
--------------------------------------------------------------------------------
1 | using Boc.Commands;
2 | using Boc.Domain;
3 | using Boc.Domain.Events;
4 | using LaYumba.Functional;
5 | using static LaYumba.Functional.F;
6 | using System;
7 | using Boc.Chapter10.Domain;
8 |
9 | namespace Boc.Chapter10.Transitions
10 | {
11 | public static class Account
12 | {
13 | // handle commands
14 |
15 | public static Transition
16 | Create(CreateAccount cmd)
17 | => _ =>
18 | {
19 | var evt = cmd.ToEvent();
20 | var newState = evt.ToAccount();
21 | return (evt, newState);
22 | };
23 |
24 |
25 | public static Transition
26 | Deposit(AcknowledgeCashDeposit cmd)
27 | => account =>
28 | {
29 | if (account.Status != AccountStatus.Active)
30 | return Errors.AccountNotActive;
31 |
32 | var evt = cmd.ToEvent();
33 | var newState = account.Apply(evt);
34 |
35 | return (evt, newState);
36 | };
37 |
38 | public static Transition
39 | SetOverdraft(SetOverdraft cmd)
40 | => account =>
41 | {
42 | var evt = cmd.ToEvent(cmd.Amount - account.AllowedOverdraft);
43 | var newState = account.Apply(evt);
44 |
45 | return (evt, newState);
46 | };
47 |
48 | public static Validation Freeze
49 | (this AccountState @this, FreezeAccount cmd)
50 | {
51 | if (@this.Status == AccountStatus.Frozen)
52 | return Errors.AccountNotActive;
53 |
54 | return cmd.ToEvent();
55 | }
56 |
57 | // apply events
58 |
59 | public static AccountState ToAccount(this CreatedAccount evt)
60 | => new AccountState
61 | (
62 | Currency: evt.Currency,
63 | Status: AccountStatus.Active
64 | );
65 |
66 | public static AccountState Apply(this AccountState @this, Event evt)
67 | => new Pattern
68 | {
69 | (DepositedCash e) => @this.Credit(e.Amount),
70 | (DebitedTransfer e) => @this.Debit(e.DebitedAmount),
71 | (FrozeAccount e) => @this.WithStatus(AccountStatus.Frozen),
72 | }
73 | .Match(evt);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Examples/Chapter10/Transitions/Transition.cs:
--------------------------------------------------------------------------------
1 | using Boc.Domain.Events;
2 | using LaYumba.Functional;
3 | using static LaYumba.Functional.F;
4 | using System;
5 |
6 | namespace Boc.Chapter10
7 | {
8 | public delegate Validation<(T, St)> Transition(St state);
9 |
10 | public static class Transition
11 | {
12 | public static Transition Select
13 | (this Transition transition, Func project)
14 | => state0
15 | => transition(state0)
16 | .Map(result => (project(result.Item1), result.Item2));
17 |
18 | public static Transition SelectMany
19 | (this Transition transition, Func> f)
20 | => state0
21 | => transition(state0)
22 | .Bind(t => f(t.Item1)(t.Item2));
23 |
24 | public static Transition SelectMany