├── .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 25 | (this Transition transition 26 | , Func> bind 27 | , Func project) 28 | => state0 29 | => transition(state0) 30 | .Bind(t => bind(t.Item1)(t.Item2) 31 | .Map(r => (project(t.Item1, r.Item1), r.Item2))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/Chapter11/Func_Map.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using LaYumba.Functional; 4 | 5 | namespace Examples.Chapter11 6 | { 7 | class Model 8 | { 9 | public Guid Id { get; } 10 | public static Model Create(Guid id) => new Model(); 11 | } 12 | class ViewModel { public Guid Id { get; } } 13 | 14 | static class Mapper 15 | { 16 | public static ViewModel ToViewModel(this Model model) => new ViewModel(); 17 | } 18 | 19 | class Func_Map_Example 20 | { 21 | IRepository cache; 22 | IRepository models; 23 | 24 | Func ModelFactory(Guid id) => () 25 | => models.Get(id).GetOrElse(Model.Create(id)); 26 | 27 | ViewModel GetOrCreate(Guid id) 28 | => cache.Get(id) 29 | .GetOrElse(ModelFactory(id).Map(Mapper.ToViewModel)); 30 | } 31 | 32 | class Func_Map_Example_More_Reasonable 33 | { 34 | IRepository cache; 35 | IRepository models; 36 | 37 | Model ModelFactory(Guid id) 38 | => models.Get(id).GetOrElse(Model.Create(id)); 39 | 40 | ViewModel GetOrCreate(Guid id) 41 | => cache.Get(id) 42 | .GetOrElse(() => ModelFactory(id).ToViewModel()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/Chapter11/Identity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | using Unit = System.ValueTuple; 4 | 5 | namespace Examples.Chapter11.Cache 6 | { 7 | using static Console; 8 | using static F; 9 | 10 | interface IRepository 11 | { 12 | Option Get(Guid id); 13 | } 14 | 15 | class CachingRepository : IRepository 16 | { 17 | private IRepository cache; 18 | private IRepository db; 19 | 20 | public Option Get(Guid id) 21 | //=> cache.Get(id).OrElse(db.Get(id)); // eagerly gets from DB, defeating the purpose of having the cache 22 | => cache.Get(id).OrElse(() => db.Get(id)); 23 | } 24 | 25 | static class Identity_Example 26 | { 27 | internal static void _main() 28 | { 29 | string value = "Anton"; 30 | Func toUpper = s => s.ToUpper(); 31 | Func print = s => { WriteLine(s); return Unit(); }; 32 | 33 | Identity(value) 34 | .Map(toUpper) 35 | .Map(print)(); 36 | 37 | ReadKey(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/Chapter11/Reader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | 4 | namespace Examples.ReaderEx 5 | { 6 | using static Console; 7 | 8 | // using the custom delegate Reader to indicate a function 9 | // that "reads" an Env and computes a T 10 | public static class Reader_Example 11 | { 12 | public static Reader FirstThing() 13 | => from name in Reader.Ask() 14 | select $"First, {name} looked up"; 15 | 16 | public static Reader SecondThing() 17 | => from name in Reader.Ask() 18 | select $"Then, {name} turned to me..."; 19 | 20 | internal static void _main() 21 | { 22 | var reader = from first in FirstThing() 23 | from second in SecondThing() 24 | select first + "\n" + second; 25 | 26 | var story = reader("Tamerlano"); 27 | 28 | WriteLine(story); 29 | } 30 | } 31 | 32 | // same, but just using Func 33 | public static class Func_As_Reader_Example 34 | { 35 | internal static void _main() 36 | { 37 | Func firstThing 38 | = name => $"First, {name} looked up"; 39 | 40 | Func secondThing 41 | = name => $"Then, {name} turned to me..."; 42 | 43 | var reader = from first in firstThing 44 | from second in secondThing 45 | select $"{first}\n{second}"; 46 | 47 | // same as above, but using explicit arguments, instead of LINQ 48 | var reader1 = firstThing 49 | .Bind(first => secondThing 50 | .Map(second => $"{first}\n{second}")); 51 | 52 | 53 | var story = reader("Tamerlano"); 54 | // => First, Tamerlano looked up 55 | // Then, Tamerlano turned to me... 56 | 57 | WriteLine(story); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Examples/Chapter11/Tap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | 4 | namespace Examples.Chapter11 5 | { 6 | class Tap_Example 7 | { 8 | IRepository cache; 9 | IRepository models; 10 | 11 | Model GetOrCreateModel(Guid id) 12 | => models.Get(id) 13 | .GetOrElse(Model.Create(id).Pipe(models.Save)); 14 | 15 | ViewModel CreateAndCacheViewModel(Guid id) 16 | => GetOrCreateModel(id).ToViewModel().Pipe(cache.Save); 17 | 18 | ViewModel GetOrCreate(Guid id) 19 | => cache.Get(id) 20 | .GetOrElse(() => CreateAndCacheViewModel(id)); 21 | } 22 | 23 | interface IRepository 24 | { 25 | Option Get(Guid id); 26 | void Save(T obj); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Chapter11/Try.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | using static LaYumba.Functional.F; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using NUnit.Framework; 7 | 8 | namespace Examples.Chapter11 9 | { 10 | public class TryTests 11 | { 12 | Exceptional Boilerplate_CreateUri(string uri) 13 | { 14 | try { return new Uri(uri); } 15 | catch (Exception ex) { return ex; } 16 | } 17 | 18 | Try CreateUri(string uri) => () => new Uri(uri); 19 | 20 | Try Parse(string s) => () => JObject.Parse(s); 21 | 22 | Try ExtractUri(string json) => 23 | from jObj in Parse(json) 24 | let uriStr = (string)jObj["Uri"] 25 | from uri in Try(() => new Uri(uriStr)) 26 | select uri; 27 | 28 | [TestCase(@"{'Uri': 'http://github.com'}", "Ok")] 29 | [TestCase("{'Uri': 'rubbish'}", "Invalid URI")] 30 | [TestCase("{}", "Value cannot be null")] 31 | [TestCase("blah!", "Unexpected character encountered")] 32 | public void SuccessfulTry(string json, string expected) 33 | => Assert.IsTrue( 34 | ExtractUri(json) 35 | .Run() 36 | .Match( 37 | ex => ex.Message, 38 | _ => "Ok") 39 | .StartsWith(expected)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Examples/Chapter12/ListNumbering.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LaYumba.Functional; 5 | 6 | namespace Examples.ReaderEx 7 | { 8 | using static Enumerable; 9 | using static F; 10 | using static Console; 11 | using static StatefulComputation; 12 | 13 | public class State_Number_List 14 | { 15 | internal static void _main() 16 | { 17 | var list = List('a', 'b', 'c', 'd'); 18 | 19 | WriteLine("recursively number list:"); 20 | 21 | var numberedList = new State_Number_List().NumberList(list)(0).Item1; 22 | numberedList.ForEach(WriteLine); 23 | 24 | WriteLine(); 25 | WriteLine("Using Zip"); 26 | var altNumberedList = list.Zip(Naturals(), Numbered.Create); 27 | altNumberedList.ForEach(WriteLine); 28 | 29 | ReadKey(); 30 | } 31 | 32 | private static StatefulComputation> NumberItem(T item) 33 | => count => (new Numbered(item, count), count + 1); 34 | 35 | // alternatively 36 | private static StatefulComputation> NumberItemLINQ(T item) 37 | => from count in Get() 38 | from _ in Put(count + 1) 39 | select new Numbered(item, count); 40 | 41 | StatefulComputation>> NumberList(IEnumerable list) 42 | => list.Match( 43 | Empty: () => StatefulComputation.Return(Empty>()), 44 | Otherwise: (x, xs) => from head in NumberItem(x) 45 | from tail in NumberList(xs) 46 | select List(head).Concat(tail)); 47 | 48 | static IEnumerable Naturals() 49 | { 50 | int i = 0; 51 | while (true) yield return i++; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Examples/Chapter13/Boc/FxController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using LaYumba.Functional; 4 | using Boc.Domain; 5 | 6 | namespace Examples.Chapter13 7 | { 8 | public class FxController : Controller 9 | { 10 | 11 | //$ curl http://localhost:5000/convert/1000/USD/to/EUR -s 12 | //896.9000 13 | 14 | //$ curl http://localhost:5000/convert/1000/USD/to/JPY -s 15 | //103089.0000 16 | 17 | //$ curl http://localhost:5000/convert/1000/XXX/to/XXX -s 18 | //{"message":"An unexpected error has occurred"} 19 | 20 | 21 | [HttpGet("convert/{amount}/{from}/to/{to}")] 22 | public Task Convert(decimal amount, string from, string to) 23 | => Yahoo.GetRate(from + to) 24 | .OrElse(() => CurrencyLayer.GetRate(from + to)) 25 | .Map(rate => amount * rate) 26 | .Map( 27 | Faulted: ex => StatusCode(500, Errors.UnexpectedError), 28 | Completed: result => Ok(result) as IActionResult); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Chapter13/Boc/MakeTransferController.cs: -------------------------------------------------------------------------------- 1 | using Boc.Commands; 2 | using LaYumba.Functional; 3 | using static LaYumba.Functional.F; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Boc.Chapter10.Domain; 7 | using Boc.Domain.Events; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Boc.Domain; 10 | using Unit = System.ValueTuple; 11 | 12 | namespace Boc.Chapter10.Services 13 | { 14 | public class TransferNowController : Controller 15 | { 16 | Func> validate; 17 | Func>> getAccount; 18 | Func saveAndPublish; 19 | 20 | Func>> GetAccount 21 | => id 22 | => getAccount(id) 23 | .Map(opt => opt.ToValidation(() => Errors.UnknownAccountId(id))); 24 | 25 | Func> SaveAndPublish => async e => 26 | { 27 | await saveAndPublish(e); 28 | return Unit(); 29 | }; 30 | 31 | public Task Transfer([FromBody] MakeTransfer command) 32 | { 33 | Task> outcome = 34 | from cmd in Async(validate(command)) 35 | from acc in GetAccount(cmd.DebitedAccountId) 36 | from result in Async(Account.Debit(acc, cmd)) 37 | from _ in SaveAndPublish(result.Item1).Map(Valid) 38 | select result.Item2; 39 | 40 | //Task> a = validate(command) 41 | // .Traverse(cmd => getAccount(cmd.DebitedAccountId) 42 | // .Bind(acc => Account.Debit(acc, cmd) 43 | // .Traverse(result => saveAndPublish(result.Item1) 44 | // .Map(_ => result.Item2)))) 45 | // .Map(vva => vva.Bind(va => va)); // flatten the nested validation inside the task 46 | 47 | return outcome.Map( 48 | Faulted: ex => StatusCode(500, Errors.UnexpectedError), 49 | Completed: val => val.Match( 50 | Invalid: errs => BadRequest(new { Errors = errs }), 51 | Valid: newState => Ok(new { Balance = newState.Balance }) as IActionResult)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Examples/Chapter13/Retry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using static System.Console; 4 | using System.Threading.Tasks; 5 | using LaYumba.Functional; 6 | 7 | namespace Examples.Chapter13 8 | { 9 | using static F; 10 | 11 | public static class RetryHelper 12 | { 13 | public enum RetryStrategy { Exponential, Fixed }; 14 | 15 | public static Task _Retry 16 | (int retries, int delayMillis, Func> start) 17 | => retries == 0 18 | ? start() 19 | : start().OrElse(() => 20 | from _ in Task.Delay(delayMillis) 21 | from t in Retry(retries - 1, delayMillis * 2, start) 22 | select t); 23 | 24 | public static Task Retry 25 | (int retries, int delayMillis, Func> start) 26 | => retries == 0 27 | ? start() 28 | : start().OrElse(async () => 29 | { 30 | await Task.Delay(delayMillis); 31 | return await Retry(retries - 1, delayMillis * 2, start); 32 | }); 33 | 34 | public static void _main() 35 | { 36 | Retry(10, 100, () => FxApi.GetRate("GBPUSD")); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Examples/Chapter13/ValidationStrategies.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | using static LaYumba.Functional.F; 3 | using System.Collections.Generic; 4 | using NUnit.Framework; 5 | using System.Linq; 6 | 7 | namespace Boc.Chapter7 8 | { 9 | using static ValidationStrategies; 10 | 11 | public static partial class ValidationStrategies 12 | { 13 | // runs all validators, accumulating all validation errors 14 | public static Validator HarvestErrorsTr 15 | (params Validator[] validators) 16 | => t 17 | => validators 18 | .Traverse(validate => validate(t)) 19 | .Map(_ => t); 20 | } 21 | 22 | public static partial class ValidationStrategiesTest 23 | { 24 | static Validator ShouldBeLowerCase 25 | => s 26 | => (s == s.ToLower()) ? Valid(s) : Error($"{s} should be lower case"); 27 | 28 | static Validator ShouldBeOfLength(int n) 29 | => s 30 | => (s.Length == n) ? Valid(s) : Error($"{s} should be of length {n}"); 31 | 32 | static Validator ValidateCountryCode 33 | = HarvestErrorsTr(ShouldBeLowerCase, ShouldBeOfLength(2)); 34 | 35 | [TestCase("us", ExpectedResult = "Valid(us)")] 36 | [TestCase("US", ExpectedResult = "Invalid([US should be lower case])")] 37 | [TestCase("USA", ExpectedResult = "Invalid([USA should be lower case, USA should be of length 2])")] 38 | public static string TestCountryCodeValidation(string s) 39 | => ValidateCountryCode(s).ToString(); 40 | 41 | public class HarvestErrors_WithTraverse_Test 42 | { 43 | [Test] 44 | public void WhenAllValidatorsSucceed_ThenSucceed() => Assert.AreEqual( 45 | actual: HarvestErrorsTr(Success, Success)(1), 46 | expected: Valid(1) 47 | ); 48 | 49 | [Test] 50 | public void WhenNoValidators_ThenSucceed() => Assert.AreEqual( 51 | actual: HarvestErrorsTr()(1), 52 | expected: Valid(1) 53 | ); 54 | 55 | [Test] 56 | public void WhenOneValidatorFails_ThenFail() => 57 | HarvestErrorsTr(Success, Failure)(1).Match( 58 | Valid: (_) => Assert.Fail(), 59 | Invalid: (errs) => Assert.AreEqual(1, errs.Count())); 60 | 61 | [Test] 62 | public void WhenSeveralValidatorsFail_ThenFail() => 63 | HarvestErrorsTr(Success, Failure, Failure, Success)(1).Match( 64 | Valid: (_) => Assert.Fail(), 65 | Invalid: (errs) => Assert.AreEqual(2, errs.Count())); // all errors are returned 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Examples/Chapter14/CreatingObservables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Subjects; 6 | using System.Threading.Tasks; 7 | 8 | using LaYumba.Functional; 9 | 10 | using static System.Console; 11 | 12 | namespace Examples.Chapter14 13 | { 14 | static class CreatingObservables 15 | { 16 | public static class Timer 17 | { 18 | public static void Run() 19 | { 20 | var oneSec = TimeSpan.FromMilliseconds(1000); 21 | 22 | var ticks = Observable.Interval(oneSec); 23 | 24 | ticks.Take(10).Subscribe(Console.WriteLine); 25 | 26 | Task.Delay(12_000).Wait(); 27 | } 28 | } 29 | 30 | public static class Subjects 31 | { 32 | public static void Run() 33 | { 34 | WriteLine("Enter some inputs to push them to 'inputs', or 'q' to quit"); 35 | 36 | var inputs = new Subject(); 37 | 38 | using (inputs.Trace("inputs")) 39 | { 40 | for (string input; (input = ReadLine()) != "q";) 41 | inputs.OnNext(input); 42 | inputs.OnCompleted(); 43 | } 44 | } 45 | } 46 | 47 | // alternative methods to capture console inputs as a stream 48 | 49 | public static class Create 50 | { 51 | public static void Run() 52 | { 53 | var inputs = Observable.Create(observer => 54 | { 55 | for (string input; (input = ReadLine()) != "q";) 56 | observer.OnNext(input); 57 | observer.OnCompleted(); 58 | 59 | return () => {}; 60 | }); 61 | 62 | inputs.Trace("inputs"); 63 | } 64 | } 65 | 66 | public static class Generate 67 | { 68 | public static void Run() 69 | { 70 | var inputs = Observable.Generate(ReadLine() 71 | , input => input != "q" 72 | , _ => ReadLine() 73 | , input => input); 74 | 75 | inputs.Trace("inputs"); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Examples/Chapter14/ObservableExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static System.Console; 3 | using System.Reactive.Linq; 4 | 5 | namespace Examples.Chapter14 6 | { 7 | public static class ObservableExt 8 | { 9 | public static IDisposable Trace(this IObservable source, string name) 10 | => source.Subscribe( 11 | onNext: val => WriteLine($"{name} -> {val}"), 12 | onError: ex => WriteLine($"{name} ERROR: {ex.Message}"), 13 | onCompleted: () => WriteLine($"{name} END")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Chapter15/Agents/Counter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using static System.Console; 3 | using LaYumba.Functional; 4 | using static LaYumba.Functional.F; 5 | using System.Threading.Tasks; 6 | 7 | namespace Examples.Agents 8 | { 9 | public class Counter 10 | { 11 | public static void PrintThreadOf(string op) 12 | => WriteLine($"{op} on thread: {Thread.CurrentThread.ManagedThreadId}"); 13 | 14 | private readonly Agent counter = 15 | Agent.Start(0, (int state, int msg) => 16 | { 17 | var newState = state + msg; 18 | PrintThreadOf("Counter"); 19 | return (newState, newState); 20 | }); 21 | 22 | // public interface of the Counter 23 | public Task IncrementBy(int by) => counter.Tell(by); 24 | 25 | public static void main() 26 | { 27 | PrintThreadOf("Main"); 28 | var counter = new Counter(); 29 | 30 | for (string input; (input = ReadLine().ToUpper()) != "Q";) 31 | { 32 | var newCount = counter.IncrementBy(int.Parse(input)).Result; 33 | PrintThreadOf("received result"); 34 | WriteLine(newCount); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Examples/Chapter15/Agents/IdealizedAgent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Threading.Tasks.Dataflow; 4 | using System.Collections.Concurrent; 5 | 6 | namespace LaYumba.Functional 7 | { 8 | // idealized implementation of an agent 9 | sealed class IdealizedAgent 10 | { 11 | BlockingCollection inbox = new BlockingCollection(new ConcurrentQueue()); 12 | 13 | public void Tell(Msg message) => inbox.Add(message); 14 | 15 | public IdealizedAgent(State initialState 16 | , Func process) 17 | { 18 | void Loop(State state) 19 | { 20 | Msg message = inbox.Take(); // dequeue a message as soon as it's available 21 | State newState = process(state, message); // process the message 22 | Loop(newState); // loop with the new state 23 | } 24 | 25 | Task.Run(() => Loop(initialState)); // the actor runs in its own process 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Chapter15/Agents/PingPongAgents.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using static System.Console; 3 | using LaYumba.Functional; 4 | using System.Threading.Tasks; 5 | 6 | namespace Examples.Agents 7 | { 8 | public class PingPongAgents 9 | { 10 | public static void main() 11 | { 12 | Agent logger, ping, pong = null; 13 | 14 | logger = Agent.Start((string msg) => WriteLine(msg)); 15 | 16 | ping = Agent.Start((string msg) => 17 | { 18 | if (msg == "STOP") return; 19 | 20 | logger.Tell($"Received '{msg}'; Sending 'PING'"); 21 | Task.Delay(500).Wait(); 22 | pong.Tell("PING"); 23 | }); 24 | 25 | pong = Agent.Start(0, (int count, string msg) => 26 | { 27 | int newCount = count + 1; 28 | string nextMsg = (newCount < 5) ? "PONG" : "STOP"; 29 | 30 | logger.Tell($"Received '{msg}' #{newCount}; Sending '{nextMsg}'"); 31 | Task.Delay(500).Wait(); 32 | ping.Tell(nextMsg); 33 | 34 | return newCount; 35 | }); 36 | 37 | ping.Tell("START"); 38 | 39 | Thread.Sleep(10000); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/Chapter15/Boc/AccountProcess.cs: -------------------------------------------------------------------------------- 1 | using Boc.Commands; 2 | using LaYumba.Functional; 3 | using static LaYumba.Functional.F; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Boc.Chapter10.Domain; 7 | using Boc.Domain.Events; 8 | using Unit = System.ValueTuple; 9 | 10 | namespace Boc.Chapter15 11 | { 12 | using Result = Validation<(Event Event, AccountState NewState)>; 13 | 14 | public class AccountProcess 15 | { 16 | Agent agent; 17 | 18 | public AccountProcess(AccountState initialState, Func> saveAndPublish) 19 | { 20 | agent = Agent.Start(initialState 21 | , async (AccountState state, Command cmd) => 22 | { 23 | Result result = new Pattern 24 | { 25 | (MakeTransfer transfer) => state.Debit(transfer), 26 | (FreezeAccount freeze) => state.Freeze(freeze), 27 | } 28 | .Match(cmd); 29 | 30 | await result.Traverse(tpl => saveAndPublish(tpl.Event)); // persist within block, so that the agent doesn't process new messages in a non-persisted state 31 | 32 | var newState = result.Map(tpl => tpl.NewState).GetOrElse(state); 33 | return (newState, result); 34 | }); 35 | } 36 | 37 | public Task Handle(Command cmd) => agent.Tell(cmd); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Examples/Chapter15/Boc/ControllerActivator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Controllers; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.AspNetCore.Mvc.Internal; 5 | using System; 6 | using Microsoft.Extensions.Configuration; 7 | using System.Linq; 8 | using Boc.Commands; 9 | using LaYumba.Functional; 10 | using static LaYumba.Functional.F; 11 | using System.Collections.Generic; 12 | using Examples; 13 | using System.Threading.Tasks; 14 | using Boc.Chapter10.Domain; 15 | using Unit = System.ValueTuple; 16 | using Boc.Domain.Events; 17 | 18 | namespace Boc.Chapter15 19 | { 20 | public class ControllerActivator : IControllerActivator 21 | { 22 | Lazy accountRegistry; 23 | 24 | Func>> loadEvents; 25 | Func> saveAndPublish; 26 | Func> validate; 27 | 28 | public ControllerActivator() 29 | { 30 | accountRegistry = new Lazy(() => 31 | new AccountRegistry 32 | ( loadAccount: id => loadEvents(id).Map(Account.From) 33 | , saveAndPublish: saveAndPublish)); 34 | } 35 | 36 | public object Create(ControllerContext context) 37 | { 38 | var type = context.ActionDescriptor.ControllerTypeInfo.AsType(); 39 | if (type.Equals(typeof(MakeTransferController))) 40 | return new MakeTransferController( 41 | getAccount: accountRegistry.Value.Lookup, 42 | validate: validate); 43 | 44 | throw new InvalidOperationException("Unexpected controller type"); 45 | } 46 | 47 | public void Release(ControllerContext context, object controller) 48 | { 49 | var disposable = controller as IDisposable; 50 | if (disposable != null) disposable.Dispose(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Examples/Chapter15/Boc/MakeTransferController.cs: -------------------------------------------------------------------------------- 1 | using Boc.Commands; 2 | using LaYumba.Functional; 3 | using static LaYumba.Functional.F; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Boc.Chapter10.Domain; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Boc.Domain; 9 | 10 | namespace Boc.Chapter15 11 | { 12 | public class MakeTransferController : Controller 13 | { 14 | public MakeTransferController(Func>> getAccount 15 | , Func> validate) 16 | { 17 | Validate = validate; 18 | GetAccount = id => getAccount(id) 19 | .Map(opt => opt.ToValidation(() => Errors.UnknownAccountId(id))); 20 | } 21 | 22 | Func> Validate; 23 | Func>> GetAccount; 24 | 25 | public Task MakeTransfer([FromBody] MakeTransfer command) 26 | { 27 | Task> outcome = 28 | from cmd in Async(Validate(command)) 29 | from acc in GetAccount(cmd.DebitedAccountId) 30 | from result in acc.Handle(cmd) 31 | select result.NewState; 32 | 33 | return outcome.Map( 34 | Faulted: ex => StatusCode(500, Errors.UnexpectedError), 35 | Completed: val => val.Match( 36 | Invalid: errs => BadRequest(new { Errors = errs }), 37 | Valid: newState => Ok(new { Balance = newState.Balance }) as IActionResult)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/Examples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | Examples.Program 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Examples/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static System.Console; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.AspNetCore.Builder; 7 | using LaYumba.Functional; 8 | 9 | using System.Reflection; 10 | using Microsoft.AspNetCore; 11 | 12 | namespace Examples 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | var cliExamples = new Dictionary 19 | { 20 | ["HOFs"] = Chapter1.HOFs.Run, 21 | ["Greetings"] = Chapter7.Greetings.Run, 22 | ["Timer"] = Chapter14.CreatingObservables.Timer.Run, 23 | ["Subjects"] = Chapter14.CreatingObservables.Subjects.Run, 24 | ["Create"] = Chapter14.CreatingObservables.Create.Run, 25 | ["Generate"] = Chapter14.CreatingObservables.Generate.Run, 26 | ["CurrencyLookup_Unsafe"] = Chapter14.CurrencyLookup_Unsafe.Run, 27 | ["CurrencyLookup_Safe"] = Chapter14.CurrencyLookup_Safe.Run, 28 | ["VoidContinuations"] = Chapter14.VoidContinuations.Run, 29 | ["KeySequences"] = Chapter14.KeySequences.Run, 30 | }; 31 | 32 | if (args.Length > 0) 33 | cliExamples.Lookup(args[0]) 34 | .Match( 35 | None: () => WriteLine($"Unknown option: '{args[0]}'"), 36 | Some: (main) => main() 37 | ); 38 | 39 | else StartWebApi(); 40 | } 41 | 42 | static void StartWebApi() 43 | { 44 | var host = WebHost.CreateDefaultBuilder() 45 | .UseStartup() 46 | .Build(); 47 | 48 | host.Run(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Examples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("877999ed-3917-4778-a535-4799b2b841d0")] 16 | -------------------------------------------------------------------------------- /Examples/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52332/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "api/values", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "Examples": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "api/values", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:52333" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Examples/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": 3 | { 4 | "Default": "DefaultConnectionString" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Debug", 10 | "System": "Information", 11 | "Microsoft": "Information" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Exercises/Chapter01/Exercises.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Exercises.Chapter1 6 | { 7 | static class Exercises 8 | { 9 | // 1. Write a function that negates a given predicate: whenvever the given predicate 10 | // evaluates to `true`, the resulting function evaluates to `false`, and vice versa. 11 | 12 | // 2. Write a method that uses quicksort to sort a `List` (return a new list, 13 | // rather than sorting it in place). 14 | 15 | // 3. Generalize your implementation to take a `List`, and additionally a 16 | // `Comparison` delegate. 17 | 18 | // 4. In this chapter, you've seen a `Using` function that takes an `IDisposable` 19 | // and a function of type `Func`. Write an overload of `Using` that 20 | // takes a `Func` as first 21 | // parameter, instead of the `IDisposable`. (This can be used to fix warnings 22 | // given by some code analysis tools about instantiating an `IDisposable` and 23 | // not disposing it.) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Exercises/Chapter02/Exercises.cs: -------------------------------------------------------------------------------- 1 | namespace Exercises.Chapter2 2 | { 3 | // 1. Write a console app that calculates a user's Body-Mass Index: 4 | // - prompt the user for her height in metres and weight in kg 5 | // - calculate the BMI as weight/height^2 6 | // - output a message: underweight(bmi<18.5), overweight(bmi>=25) or healthy weight 7 | // 2. Structure your code so that structure it so that pure and impure parts are separate 8 | // 3. Unit test the pure parts 9 | // 4. Unit test the impure parts using the HOF-based approach 10 | 11 | public static class Bmi 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Exercises/Chapter02/Solutions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | 4 | namespace Exercises.Chapter2.Solutions 5 | { 6 | using static Console; 7 | using static Math; 8 | 9 | public enum BmiRange { Underweight, Healthy, Overweight } 10 | 11 | static class Bmi 12 | { 13 | public static void Run() 14 | { 15 | Run(Read, Write); 16 | } 17 | 18 | internal static void Run(Func read, Action write) 19 | { 20 | // input 21 | double weight = read("weight") 22 | , height = read("height"); 23 | 24 | // computation 25 | var bmiRange = CalculateBmi(height, weight).ToBmiRange(); 26 | 27 | // output 28 | write(bmiRange); 29 | } 30 | 31 | internal static double CalculateBmi(double height, double weight) 32 | => Round(weight / Pow(height, 2), 2); 33 | 34 | internal static BmiRange ToBmiRange(this double bmi) 35 | => bmi < 18.5 ? BmiRange.Underweight 36 | : 25 <= bmi ? BmiRange.Overweight 37 | : BmiRange.Healthy; 38 | 39 | private static double Read(string field) 40 | { 41 | WriteLine($"Please enter your {field}"); 42 | return double.Parse(ReadLine()); 43 | } 44 | 45 | private static void Write(BmiRange bmiRange) 46 | => WriteLine($"Based on your BMI, you are {bmiRange}"); 47 | } 48 | 49 | public class BmiTests 50 | { 51 | [TestCase(1.80, 77, ExpectedResult = 23.77)] 52 | [TestCase(1.60, 77, ExpectedResult = 30.08)] 53 | public double CalculateBmi(double height, double weight) 54 | => Bmi.CalculateBmi(height, weight); 55 | 56 | [TestCase(23.77, ExpectedResult = BmiRange.Healthy)] 57 | [TestCase(30.08, ExpectedResult = BmiRange.Overweight)] 58 | public BmiRange ToBmiRange(double bmi) => bmi.ToBmiRange(); 59 | 60 | [TestCase(1.80, 77, ExpectedResult = BmiRange.Healthy)] 61 | [TestCase(1.60, 77, ExpectedResult = BmiRange.Overweight)] 62 | public BmiRange ReadBmi(double height, double weight) 63 | { 64 | var result = default(BmiRange); 65 | Func read = s => s == "height" ? height : weight; 66 | Action write = r => result = r; 67 | 68 | Bmi.Run(read, write); 69 | return result; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Exercises/Chapter03/Exercises.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | //using System.Configuration; 4 | using LaYumba.Functional; 5 | 6 | namespace Exercises.Chapter3 7 | { 8 | public static class Exercises 9 | { 10 | // 1 Write a generic function that takes a string and parses it as a value of an enum. It 11 | // should be usable as follows: 12 | 13 | // Enum.Parse("Friday") // => Some(DayOfWeek.Friday) 14 | // Enum.Parse("Freeday") // => None 15 | 16 | // 2 Write a Lookup function that will take an IEnumerable and a predicate, and 17 | // return the first element in the IEnumerable that matches the predicate, or None 18 | // if no matching element is found. Write its signature in arrow notation: 19 | 20 | // bool isOdd(int i) => i % 2 == 1; 21 | // new List().Lookup(isOdd) // => None 22 | // new List { 1 }.Lookup(isOdd) // => Some(1) 23 | 24 | // 3 Write a type Email that wraps an underlying string, enforcing that it’s in a valid 25 | // format. Ensure that you include the following: 26 | // - A smart constructor 27 | // - Implicit conversion to string, so that it can easily be used with the typical API 28 | // for sending emails 29 | 30 | // 4 Take a look at the extension methods defined on IEnumerable inSystem.LINQ.Enumerable. 31 | // Which ones could potentially return nothing, or throw some 32 | // kind of not-found exception, and would therefore be good candidates for 33 | // returning an Option instead? 34 | } 35 | 36 | // 5. Write implementations for the methods in the `AppConfig` class 37 | // below. (For both methods, a reasonable one-line method body is possible. 38 | // Assume settings are of type string, numeric or date.) Can this 39 | // implementation help you to test code that relies on settings in a 40 | // `.config` file? 41 | public class AppConfig 42 | { 43 | NameValueCollection source; 44 | 45 | //public AppConfig() : this(ConfigurationManager.AppSettings) { } 46 | 47 | public AppConfig(NameValueCollection source) 48 | { 49 | this.source = source; 50 | } 51 | 52 | public Option Get(string name) 53 | { 54 | throw new NotImplementedException("your implementation here..."); 55 | } 56 | 57 | public T Get(string name, T defaultValue) 58 | { 59 | throw new NotImplementedException("your implementation here..."); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Exercises/Chapter04/Exercises.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using LaYumba.Functional; 5 | 6 | namespace Exercises.Chapter4 7 | { 8 | static class Exercises 9 | { 10 | // 1 Implement Map for ISet and IDictionary. (Tip: start by writing down 11 | // the signature in arrow notation.) 12 | 13 | // 2 Implement Map for Option and IEnumerable in terms of Bind and Return. 14 | 15 | // 3 Use Bind and an Option-returning Lookup function (such as the one we defined 16 | // in chapter 3) to implement GetWorkPermit, shown below. 17 | 18 | // Then enrich the implementation so that `GetWorkPermit` 19 | // returns `None` if the work permit has expired. 20 | 21 | static Option GetWorkPermit(Dictionary people, string employeeId) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | // 4 Use Bind to implement AverageYearsWorkedAtTheCompany, shown below (only 27 | // employees who have left should be included). 28 | 29 | static double AverageYearsWorkedAtTheCompany(List employees) 30 | { 31 | // your implementation here... 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | 36 | public struct WorkPermit 37 | { 38 | public string Number { get; set; } 39 | public DateTime Expiry { get; set; } 40 | } 41 | 42 | public class Employee 43 | { 44 | public string Id { get; set; } 45 | public Option WorkPermit { get; set; } 46 | 47 | public DateTime JoinedOn { get; } 48 | public Option LeftOn { get; } 49 | } 50 | } -------------------------------------------------------------------------------- /Exercises/Chapter04/Person.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | 3 | namespace Exercises.Chapter4 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 | } 22 | -------------------------------------------------------------------------------- /Exercises/Chapter05/Exercises.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | using static LaYumba.Functional.F; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using NUnit.Framework; 7 | using Examples.Chapter3; 8 | 9 | namespace Exercises.Chapter5 10 | { 11 | public static class Exercises 12 | { 13 | // 1. Without looking at any code or documentation (or intllisense), write the function signatures of 14 | // `OrderByDescending`, `Take` and `Average`, which we used to implement `AverageEarningsOfRichestQuartile`: 15 | static decimal AverageEarningsOfRichestQuartile(List population) 16 | => population 17 | .OrderByDescending(p => p.Earnings) 18 | .Take(population.Count/4) 19 | .Select(p => p.Earnings) 20 | .Average(); 21 | 22 | // 2 Check your answer with the MSDN documentation: https://docs.microsoft.com/ 23 | // en-us/dotnet/api/system.linq.enumerable. How is Average different? 24 | 25 | // 3 Implement a general purpose Compose function that takes two unary functions 26 | // and returns the composition of the two. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Exercises/Chapter05/Solutions.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | using static LaYumba.Functional.F; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using NUnit.Framework; 7 | using Examples.Chapter3; 8 | 9 | namespace Exercises.Chapter5.Solutions 10 | { 11 | static class Exercises 12 | { 13 | // 1. Without looking at any code or documentation (or intllisense), write the function signatures of 14 | // `OrderByDescending`, `Take` and `Average`, which we used to implement `AverageEarningsOfRichestQuartile`: 15 | static decimal AverageEarningsOfRichestQuartile(List population) 16 | => population 17 | .OrderByDescending(p => p.Earnings) 18 | .Take(population.Count/4) 19 | .Select(p => p.Earnings) 20 | .Average(); 21 | 22 | // OrderByDescending : (IEnumerable, (T -> decimal)) -> IEnumerable 23 | // particularized for this case: 24 | // OrderByDescending : (IEnumerable, (Person -> decimal)) -> IEnumerable 25 | 26 | // Take : (IEnumerable, int) -> IEnumerable 27 | // particularized for this case: 28 | // Take : (IEnumerable, int) -> IEnumerable 29 | 30 | // Select : (IEnumerable, (T -> R)) -> IEnumerable 31 | // particularized for this case: 32 | // Select : (IEnumerable, (Person -> decimal)) -> IEnumerable 33 | 34 | // Average : IEnumerable -> T 35 | // particularized for this case: 36 | // Average : IEnumerable -> decimal 37 | 38 | 39 | // 2 Check your answer with the MSDN documentation: https://docs.microsoft.com/ 40 | // en-us/dotnet/api/system.linq.enumerable. How is Average different? 41 | 42 | // Average is the only method call that does not return an IEnumerable; 43 | // this also means that Average is the only greedy method and causes all the 44 | // previous ones in the chain to be evaluated 45 | 46 | 47 | // 3 Implement a general purpose Compose function that takes two unary functions 48 | // and returns the composition of the two. 49 | 50 | static Func Compose(this Func g, Func f) 51 | => x => g(f(x)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Exercises/Chapter06/Exercises.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | using static LaYumba.Functional.F; 3 | using System; 4 | 5 | namespace Exercises.Chapter6 6 | { 7 | static class Exercises 8 | { 9 | // 1. Write a `ToOption` extension method to convert an `Either` into an 10 | // `Option`. Then write a `ToEither` method to convert an `Option` into an 11 | // `Either`, with a suitable parameter that can be invoked to obtain the 12 | // appropriate `Left` value, if the `Option` is `None`. (Tip: start by writing 13 | // the function signatures in arrow notation) 14 | 15 | // 2. Take a workflow where 2 or more functions that return an `Option` 16 | // are chained using `Bind`. 17 | 18 | // Then change the first one of the functions to return an `Either`. 19 | 20 | // This should cause compilation to fail. Since `Either` can be 21 | // converted into an `Option` as we have done in the previous exercise, 22 | // write extension overloads for `Bind`, so that 23 | // functions returning `Either` and `Option` can be chained with `Bind`, 24 | // yielding an `Option`. 25 | 26 | 27 | // 3. Write a function `Safely` of type ((() → R), (Exception → L)) → Either that will 28 | // run the given function in a `try/catch`, returning an appropriately 29 | // populated `Either`. 30 | 31 | // 4. Write a function `Try` of type (() → T) → Exceptional that will 32 | // run the given function in a `try/catch`, returning an appropriately 33 | // populated `Exceptional`. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Exercises/Chapter07/Exercises.cs: -------------------------------------------------------------------------------- 1 | using LaYumba.Functional; 2 | using NUnit.Framework; 3 | using System; 4 | 5 | namespace Exercises.Chapter7 6 | { 7 | static class Exercises 8 | { 9 | // 1. Partial application with a binary arithmethic function: 10 | // Write a function `Remainder`, that calculates the remainder of 11 | // integer division(and works for negative input values!). 12 | 13 | // Notice how the expected order of parameters is not the 14 | // one that is most likely to be required by partial application 15 | // (you are more likely to partially apply the divisor). 16 | 17 | // Write an `ApplyR` function, that gives the rightmost parameter to 18 | // a given binary function (try to write it without looking at the implementation for `Apply`). 19 | // Write the signature of `ApplyR` in arrow notation, both in curried and non-curried form 20 | 21 | // Use `ApplyR` to create a function that returns the 22 | // remainder of dividing any number by 5. 23 | 24 | // Write an overload of `ApplyR` that gives the rightmost argument to a ternary function 25 | 26 | 27 | // 2. Let's move on to ternary functions. Define a class `PhoneNumber` with 3 28 | // fields: number type(home, mobile, ...), country code('it', 'uk', ...), and number. 29 | // `CountryCode` should be a custom type with implicit conversion to and from string. 30 | 31 | // Now define a ternary function that creates a new number, given values for these fields. 32 | // What's the signature of your factory function? 33 | 34 | // Use partial application to create a binary function that creates a UK number, 35 | // and then again to create a unary function that creates a UK mobile 36 | 37 | 38 | // 3. Functions everywhere. You may still have a feeling that objects are ultimately 39 | // more powerful than functions. Surely, a logger object should expose methods 40 | // for related operations such as Debug, Info, Error? 41 | // To see that this is not necessarily so, challenge yourself to write 42 | // a very simple logging mechanism without defining any classes or structs. 43 | // You should still be able to inject a Log value into a consumer class/function, 44 | // exposing operations like Debug, Info, and Error, like so: 45 | 46 | //static void ConsumeLog(Log log) 47 | // => log.Info("look! no objects!"); 48 | 49 | enum Level { Debug, Info, Error } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Exercises/Chapter08/Exercises.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | 4 | using Unit = System.ValueTuple; 5 | 6 | namespace Exercises.Chapter10 7 | { 8 | public static class Exercises 9 | { 10 | // 1. Implement `Apply` for `Either` and `Exceptional`. 11 | 12 | // see LaYumba.Functional/Either.cs and LaYumba.Functional/Exceptional.cs 13 | 14 | // 2. Implement the query pattern for `Either` and `Exceptional`. Try to 15 | // write down the signatures for `Select` and `SelectMany` without 16 | // looking at any examples. For the implementation, just follow the 17 | // types--if it type checks, it’s probably right! 18 | 19 | // see LaYumba.Functional/Either.cs and LaYumba.Functional/Exceptional.cs 20 | 21 | // 3. Come up with a scenario in which various `Either`-returning 22 | // operations are chained with `Bind`. (If you’re short of ideas, you can 23 | // use the favorite-dish example from Examples/Chapter08/CookFavouriteFood.) 24 | // Rewrite the code using a LINQ expression. 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Exercises/Chapter08/Solutions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LaYumba.Functional; 3 | 4 | using Unit = System.ValueTuple; 5 | using Reason = System.String; 6 | 7 | using static System.Console; 8 | 9 | namespace Exercises.Chapter10.Solutions 10 | { 11 | public static class Solutions 12 | { 13 | // 1. Implement `Apply` for `Either` and `Exceptional`. 14 | 15 | // see LaYumba.Functional/Either.cs and LaYumba.Functional/Exceptional.cs 16 | 17 | // 2. Implement the query pattern for `Either` and `Exceptional`. Try to 18 | // write down the signatures for `Select` and `SelectMany` without 19 | // looking at any examples. For the implementation, just follow the 20 | // types--if it type checks, it’s probably right! 21 | 22 | // see LaYumba.Functional/Either.cs and LaYumba.Functional/Exceptional.cs 23 | 24 | // 3. Come up with a scenario in which various `Either`-returning 25 | // operations are chained with `Bind`. (If you’re short of ideas, you can 26 | // use the favorite-dish example from <>.) Rewrite the code using a 27 | // LINQ expression. 28 | 29 | static Either PrepareFavoriteDish 30 | => from _ in WakeUpEarly() 31 | from ingredients in ShopForIngredients() 32 | from dish in CookRecipe(ingredients) 33 | select dish; 34 | 35 | static void ConsumeFavoriteDish() 36 | => PrepareFavoriteDish.Match 37 | ( 38 | Left: ComplainAbout, 39 | Right: Enjoy 40 | ); 41 | 42 | static Either WakeUpEarly() => throw new NotImplementedException(); 43 | static Either ShopForIngredients() => throw new NotImplementedException(); 44 | static Either CookRecipe(Ingredients ingredients) => throw new NotImplementedException(); 45 | 46 | static void Enjoy(Food food) => WriteLine("Mmmm... yummy!"); 47 | static void ComplainAbout(Reason reason) => WriteLine($"Oh boy, {reason}"); 48 | 49 | class Ingredients { } 50 | class Food { } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Exercises/Chapter09/Exercises.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | 4 | using LaYumba.Functional; 5 | using LaYumba.Functional.Data.LinkedList; 6 | using static LaYumba.Functional.Data.LinkedList.LinkedList; 7 | 8 | using LaYumba.Functional.Data.BinaryTree; 9 | using static LaYumba.Functional.Data.BinaryTree.Tree; 10 | 11 | namespace Exercises.Chapter9 12 | { 13 | static class Exercises 14 | { 15 | // LISTS 16 | 17 | // Implement functions to work with the singly linked List defined in this chapter: 18 | // Tip: start by writing the function signature in arrow-notation 19 | 20 | // InsertAt inserts an item at the given index 21 | 22 | // RemoveAt removes the item at the given index 23 | 24 | // TakeWhile takes a predicate, and traverses the list yielding all items until it find one that fails the predicate 25 | 26 | // DropWhile works similarly, but excludes all items at the front of the list 27 | 28 | 29 | // complexity: 30 | // InsertAt: 31 | // RemoveAt: 32 | // TakeWhile: 33 | // DropWhile: 34 | 35 | // number of new objects required: 36 | // InsertAt: 37 | // RemoveAt: 38 | // TakeWhile: 39 | // DropWhile: 40 | 41 | // TakeWhile and DropWhile are useful when working with a list that is sorted 42 | // and you’d like to get all items greater/smaller than some value; write implementations 43 | // that take an IEnumerable rather than a List 44 | 45 | 46 | // TREES 47 | 48 | // Is it possible to define `Bind` for the binary tree implementation shown in this 49 | // chapter? If so, implement `Bind`, else explain why it’s not possible (hint: start by writing 50 | // the signature; then sketch binary tree and how you could apply a tree-returning funciton to 51 | // each value in the tree). 52 | 53 | // Implement a LabelTree type, where each node has a label of type string and a list of subtrees; 54 | // this could be used to model a typical navigation tree or a cateory tree in a website 55 | 56 | // Imagine you need to add localization to your navigation tree: you're given a `LabelTree` where 57 | // the value of each label is a key, and a dictionary that maps keys 58 | // to translations in one of the languages that your site must support 59 | // (hint: define `Map` for `LabelTree` and use it to obtain the localized navigation/category tree) 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /Exercises/Exercises.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | Exercises.Program 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Exercises/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Exercises 2 | { 3 | public class Program 4 | { 5 | public static void Main(string[] args) 6 | { 7 | // run the program you've written, for example: 8 | Chapter2.Solutions.Bmi.Run(); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Exercises/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("c21d2aa1-e2d0-44e9-978e-6ba2be502224")] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Enrico Buonanno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LaYumba.Functional.Data/LaYumba.Functional.Data.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.6 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /LaYumba.Functional.Data/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("539bb3da-f698-46de-a10c-9b63848dba25")] 16 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Assertions.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LaYumba.Functional.Tests 4 | { 5 | internal static class Assertions 6 | { 7 | public static void Succeed() {; } 8 | public static void Fail() => Assert.True(false); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/BinaryTreeTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using LaYumba.Functional.Data.BinaryTree; 3 | 4 | namespace LaYumba.Functional.Tests 5 | { 6 | using static Tree; 7 | 8 | 9 | public class BinaryTreeTest 10 | { 11 | [Fact] 12 | public void MapLeaf() 13 | { 14 | var tree = Leaf(3); 15 | 16 | var actual = tree.Map(i => i + 1); 17 | var expected = Leaf(4); 18 | 19 | Assert.Equal(expected, actual); 20 | } 21 | 22 | [Fact] 23 | public void MapBranch() 24 | { 25 | var tree = Branch( 26 | Branch(Leaf(1), Leaf(2)), 27 | Leaf(3) 28 | ); 29 | 30 | var actual = tree.Map(i => i + 1); 31 | 32 | var expected = Branch( 33 | Branch(Leaf(2), Leaf(3)), 34 | Leaf(4) 35 | ); 36 | 37 | Assert.Equal(expected, actual); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Coyo.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using LaYumba.Functional.Data.BinaryTree; 3 | 4 | namespace LaYumba.Functional.Tests 5 | { 6 | using static Tree; 7 | 8 | 9 | public class CoyoTest 10 | { 11 | [Fact] 12 | public void Check() 13 | { 14 | var tree = Branch( 15 | Branch(Leaf(1), Leaf(2)), 16 | Leaf(3) 17 | ); 18 | 19 | var coyoTree = Coyo.Of, int>(tree) 20 | .Map(i => i + 1) 21 | .Map(i => i * 10); 22 | 23 | var expected = Branch( 24 | Branch(Leaf(20), Leaf(30)), 25 | Leaf(40) 26 | ); 27 | 28 | Assert.Equal(expected, coyoTree.Run()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/EnumerableExtTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FsCheck; 3 | using FsCheck.Xunit; 4 | using Xunit; 5 | using static LaYumba.Functional.F; 6 | 7 | namespace LaYumba.Functional.Tests 8 | { 9 | public class EnumerableExtTests 10 | { 11 | [Property] 12 | public void Find_AnyInNonEmptyStrings_First(NonEmptyArray> list) 13 | => Assert.Equal(Some(list.Get[0].Get), list.Get.Map(x => x.Get).Find(_ => true)); 14 | 15 | [Property] 16 | public void Find_AnyInNonEmptyDecimals_First(NonEmptyArray list) 17 | => Assert.Equal(Some(list.Get[0]), list.Get.Find(_ => true)); 18 | 19 | [Fact] 20 | public void Find_EmptyStrings_None() 21 | => Assert.Equal(None, Enumerable.Empty().Find(_ => true)); 22 | 23 | [Fact] 24 | public void Find_EmptyDecimals_None() 25 | => Assert.Equal(None, Enumerable.Empty().Find(_ => true)); 26 | } 27 | } -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/FTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace LaYumba.Functional.Tests 5 | { 6 | using static F; 7 | 8 | 9 | public class FTest 10 | { 11 | private const string HELLO = "hello"; 12 | readonly Func isHello = s => s == HELLO; 13 | 14 | [Fact] 15 | public void NegateTest() 16 | { 17 | Assert.True(isHello(HELLO)); 18 | Assert.False(isHello.Negate()(HELLO)); 19 | } 20 | 21 | [Fact] 22 | public void TestListReturnFunction() 23 | { 24 | var emptyList = List(); 25 | Assert.Equal(new string[] { }, emptyList); 26 | 27 | var singletonList = List("Andrej"); 28 | Assert.Equal(new[] { "Andrej" }, singletonList); 29 | 30 | var multiList = List("Andrej", "Natasha"); 31 | Assert.Equal(new[] { "Andrej", "Natasha" }, multiList); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/IEnumerable/FunctorLaws.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using FsCheck.Xunit; 3 | using System; 4 | 5 | namespace LaYumba.Functional.Tests 6 | { 7 | using static F; 8 | using static Assert; 9 | 10 | public class IEnumerable_FunctorLaws 11 | { 12 | [Property] 13 | public void FirstFunctorLawHolds(string s) 14 | { 15 | var a = List(s); 16 | var b = a.Map(x => x); 17 | Equal(a, b); 18 | } 19 | 20 | [Property] 21 | public void SecondFunctorLawHolds(int input) 22 | { 23 | Func f = i => i - 2; 24 | Func g = i => i * 50; 25 | 26 | var a = List(input).Map(f).Map(g); 27 | var b = List(input).Map(x => g(f(x))); 28 | 29 | Equal(a, b); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/IEnumerable/MonadLaws.cs: -------------------------------------------------------------------------------- 1 | using FsCheck.Xunit; 2 | using FsCheck; 3 | using System; 4 | using System.Collections.Generic; 5 | using Xunit; 6 | 7 | namespace LaYumba.Functional.Tests 8 | { 9 | using static F; 10 | 11 | public class IEnumerable_MonadLaws 12 | { 13 | [Property] 14 | public void RightIdentityHolds(string s) 15 | { 16 | Func> Return = i => List(i); 17 | 18 | var a = Return(s); 19 | var b = a.Bind(Return); 20 | Assert.Equal(a, b); 21 | } 22 | 23 | [Property]void LeftIdentityHolds(int x) 24 | { 25 | // given a world crossing function f... 26 | Func> f = i => Range(0, i); 27 | 28 | // then applying f to a value x 29 | // is the same as lifting x and binding f to it 30 | Assert.Equal(List(x).Bind(f), f(x)); 31 | } 32 | 33 | [Property] 34 | public void LeftIdentityHoldsRefType(NonNull nonNull) 35 | { 36 | var x = nonNull.Get; 37 | 38 | // given a world crossing function f... 39 | Func> f = s => s.ToUpper(); 40 | 41 | // then applying f to a value x 42 | // is the same as lifting x and binding f to it 43 | Assert.Equal( 44 | List(x).Bind(f), 45 | f(x) 46 | ); 47 | } 48 | 49 | [Property] 50 | public void AssociativityHolds(NonNull input) 51 | { 52 | Func> f = s => s; 53 | Func> g = c => List(c, c, c); 54 | 55 | // (m `bind` f) `bind` g == 56 | // m `bind` (f `bind` g) -- not correct, but if we expand f we get: 57 | // m `bind` (x => f(x) `bind` g) 58 | 59 | Assert.Equal 60 | ( List(input.Get).Bind(f).Bind(g) 61 | , List(input.Get).Bind(x => f(x).Bind(g)) 62 | ); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Immutable.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LaYumba.Functional.Tests 4 | { 5 | using static Assert; 6 | 7 | class A 8 | { 9 | public int B { get; } 10 | public string C { get; } 11 | public A(int b, string c) { B = b; C = c; } 12 | } 13 | 14 | 15 | public class Immutable_With_PropertyName 16 | { 17 | A original = new A(123, "hello"); 18 | 19 | [Fact] public void ItChangesTheDesiredProperty() 20 | { 21 | var @new = original.With("B", 777); 22 | 23 | Assert.Equal(777, @new.B); // updated 24 | Assert.Equal("hello", @new.C); // same as original 25 | } 26 | 27 | [Fact] public void ItDoesNotChangesTheOriginal() 28 | { 29 | var @new = original.With("B", 777); 30 | 31 | Assert.Equal(123, original.B); 32 | Assert.Equal("hello", original.C); 33 | } 34 | } 35 | 36 | 37 | public class Immutable_With_PropertyExpression 38 | { 39 | A original = new A(123, "hello"); 40 | 41 | [Fact] 42 | public void ItChangesTheDesiredProperty() 43 | { 44 | var @new = original.With(a => a.C, "hi"); 45 | 46 | Assert.Equal(123, original.B); 47 | Assert.Equal("hello", original.C); 48 | 49 | Assert.Equal(123, @new.B); 50 | Assert.Equal("hi", @new.C); 51 | } 52 | 53 | [Fact] 54 | public void YouCanChainWith() 55 | { 56 | var @new = original 57 | .With(a => a.B, 345) 58 | .With(a => a.C, "howdy"); 59 | 60 | Assert.Equal(345, @new.B); 61 | Assert.Equal("howdy", @new.C); } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/LaYumba.Functional.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Option/FunctorLaws.cs: -------------------------------------------------------------------------------- 1 | using FsCheck.Xunit; 2 | using System; 3 | using Xunit; 4 | 5 | namespace LaYumba.Functional.Tests 6 | { 7 | using static F; 8 | 9 | public static class Option_FunctorLaws 10 | { 11 | [Property(Arbitrary = new[] { typeof(ArbitraryOption) })] 12 | static void FirstFunctorLawHolds(Option a) 13 | => Assert.Equal(a, a.Map(x => x)); 14 | 15 | [Property(Arbitrary = new[] { typeof(ArbitraryOption) })] 16 | static void SecondFunctorLawHolds(Option val) 17 | { 18 | Func f = i => i - 2; 19 | Func g = i => i * 50; 20 | // h = f `andThen` g 21 | Func h = i => g(f(i)); 22 | 23 | Assert.Equal( 24 | val.Map(f).Map(g), 25 | val.Map(h) 26 | ); 27 | } 28 | 29 | public static Option MapInTermsOfApply 30 | (this Option @this, Func func) 31 | => Some(func).Apply(@this); 32 | 33 | [Property] 34 | static void SecondFunctorLawHolds_WhenMapIsDefinedInTermsOfApply(int input) 35 | { 36 | Func f = i => i - 2; 37 | Func g = i => i * 50; 38 | // h = f `andThen` g 39 | Func h = i => g(f(i)); 40 | 41 | Assert.Equal( 42 | Some(input).MapInTermsOfApply(f).MapInTermsOfApply(g), 43 | Some(input).MapInTermsOfApply(h) 44 | ); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Option/MonadLaws.cs: -------------------------------------------------------------------------------- 1 | using FsCheck.Xunit; 2 | using FsCheck; 3 | using System; 4 | using System.Collections.Generic; 5 | using Xunit; 6 | using LaYumba.Functional; 7 | 8 | namespace LaYumba.Functional.Tests 9 | { 10 | using static F; 11 | 12 | public class Option_MonadLaws 13 | { 14 | [Property(Arbitrary = new[] { typeof(ArbitraryOption) })] 15 | void RightIdentityHolds(Option m) => Assert.Equal( 16 | m, 17 | m.Bind(Some) 18 | ); 19 | 20 | [Property] 21 | public void LeftIdentityHolds(int x) 22 | { 23 | // given a world crossing function f... 24 | Func> f = i 25 | => i % 2 == 0 ? Some(i) : None; 26 | 27 | // then applying f to a value x 28 | // is the same as lifting x and binding f to it 29 | Assert.Equal(Some(x).Bind(f), f(x)); 30 | } 31 | 32 | [Property] // only works for non-null, given my implementation of Some 33 | public void LeftIdentityHoldsRefValues(NonNull x) 34 | { 35 | // given a world crossing function f... 36 | Func> f = s => Some($"{s} World"); 37 | 38 | // then applying f to a value x 39 | // is the same as lifting x and binding f to it 40 | Assert.Equal(Some(x.Get).Bind(f), f(x.Get)); 41 | } 42 | 43 | // 3. Associativity 44 | 45 | // (m `bind` f) `bind` g 46 | // m `bind` (f `bind` g) -- not correct, but if we expand f we get: 47 | // m `bind` (x => f(x) `bind` g) 48 | 49 | Func> safeSqrt = d 50 | => d < 0 ? None : Some(Math.Sqrt(d)); 51 | 52 | [Property(Arbitrary = new[] { typeof(ArbitraryOption) })] 53 | void AssociativityHolds(Option m) => Assert.Equal( 54 | m.Bind(Double.Parse).Bind(safeSqrt), 55 | m.Bind(x => Double.Parse(x).Bind(safeSqrt)) 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Pattern.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LaYumba.Functional.Tests 4 | { 5 | public class PatternTest 6 | { 7 | class Super { } 8 | class SubA : Super { } 9 | class SubB : Super { } 10 | 11 | [Fact] 12 | public void ItMatchesTheCorrectType() 13 | { 14 | Super a = new SubA() 15 | , b = new SubB(); 16 | 17 | var patt = new Pattern 18 | { 19 | (SubA _) => "a", 20 | (SubB _) => "b", 21 | }; 22 | 23 | Assert.Equal("a", patt.Match(a)); 24 | Assert.Equal("b", patt.Match(b)); 25 | } 26 | 27 | [Fact] 28 | public void ItCanHaveADefaultClause() 29 | { 30 | Super a = new SubA() 31 | , b = new SubB(); 32 | 33 | var patt = new Pattern 34 | { 35 | (SubA _) => "a", 36 | (SubB _) => "b", 37 | } 38 | .Default(() => "other"); 39 | 40 | Assert.Equal("a", patt.Match(a)); 41 | Assert.Equal("b", patt.Match(b)); 42 | Assert.Equal("other", patt.Match("hello")); 43 | } 44 | 45 | [Fact] 46 | public void DefaultClauseCanTakeAValue() 47 | { 48 | var patt = new Pattern 49 | { 50 | (SubA _) => "a", 51 | (SubB _) => "b", 52 | } 53 | .Default("other"); 54 | 55 | Assert.Equal("other", patt.Match("hello")); 56 | } 57 | 58 | [Fact] 59 | public void FirstMatchingClauseWins() 60 | { 61 | Super a = new SubA() 62 | , b = new SubB(); 63 | 64 | var patt = new Pattern 65 | { 66 | (SubA _) => "a", 67 | (object _) => "other", 68 | (SubB _) => "b", 69 | }; 70 | 71 | Assert.Equal("a", patt.Match(a)); 72 | Assert.Equal("other", patt.Match(b)); 73 | } 74 | 75 | [Fact] 76 | public void ItThrowsWhenNoMatchSpecified() 77 | { 78 | Super b = new SubB(); 79 | 80 | var patt = new Pattern 81 | { 82 | (SubA _) => "a", 83 | }; 84 | 85 | Assert.Throws(() => patt.Match(b)); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("594bacd9-54e2-403b-99a7-725a13b72be8")] 12 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/TestUtils.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace LaYumba.Functional.Tests 4 | { 5 | public static class TestUtils 6 | { 7 | public static void Fail() => Assert.Equal(true, false); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Try.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using System; 3 | using static LaYumba.Functional.F; 4 | 5 | namespace LaYumba.Functional.Tests 6 | { 7 | using static TestUtils; 8 | 9 | public class TryTests 10 | { 11 | Try CreateUri(string uri) => () => new Uri(uri); 12 | 13 | [Fact] 14 | public void SuccessfulTry() 15 | { 16 | var uriTry = CreateUri("http://github.com"); 17 | 18 | uriTry.Run().Match( 19 | Success: uri => Assert.NotNull(uri), 20 | Exception: ex => Fail() 21 | ); 22 | } 23 | 24 | [Fact] 25 | public void FailingTry() 26 | { 27 | var uriTry = CreateUri("rubbish"); 28 | 29 | uriTry.Run().Match( 30 | Success: uri => Fail(), 31 | Exception: ex => Assert.NotNull(ex) 32 | ); 33 | } 34 | 35 | [Fact] 36 | public void ItIsLazy() 37 | { 38 | bool tried = false; 39 | 40 | Func> createUri = (uri) => Try(() => 41 | { 42 | tried = true; 43 | return new Uri(uri); 44 | }); 45 | 46 | var uriTry = createUri("http://github.com"); 47 | Assert.False(tried, "creating a Try should not run it"); 48 | 49 | var schemeTry = uriTry.Map(uri => uri.Scheme); 50 | Assert.False(tried, "mapping onto a try should not run it"); 51 | 52 | uriTry.Run().Match( 53 | Success: uri => Assert.NotNull(uri), 54 | Exception: ex => Assert.True(false, "should have succeeded") 55 | ); 56 | Assert.True(tried, "matching should run the Try"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /LaYumba.Functional.Tests/Unit.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Unit = System.ValueTuple; 3 | 4 | namespace LaYumba.Functional.Tests 5 | { 6 | 7 | public class Unit_Test 8 | { 9 | [Fact] 10 | public void ThereCanOnlyBeOneUnit() 11 | { 12 | Assert.Equal(new Unit(), F.Unit()); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /LaYumba.Functional/ActionExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unit = System.ValueTuple; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public static class ActionExt 9 | { 10 | public static Func ToFunc(this Action action) 11 | => () => { action(); return Unit(); }; 12 | 13 | public static Func ToFunc(this Action action) 14 | => t => { action(t); return Unit(); }; 15 | 16 | public static Func ToFunc(this Action action) 17 | => (T1 t1, T2 t2) => { action(t1, t2); return Unit(); }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LaYumba.Functional/Atom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | public sealed class Atom 7 | where T : class 8 | { 9 | private volatile T value; 10 | public T Value => value; 11 | 12 | public Atom(T value) 13 | { 14 | this.value = value; 15 | } 16 | 17 | public T Swap(Func update) 18 | { 19 | T original, updated; 20 | original = value; 21 | updated = update(original); 22 | 23 | if (original != Interlocked.CompareExchange(ref value, updated, original)) // try once 24 | { 25 | var spinner = new SpinWait(); // if we fail, go into a spin wait, spin, and try again until succeed 26 | do 27 | { 28 | spinner.SpinOnce(); 29 | original = value; 30 | updated = update(original); 31 | } 32 | while (original != 33 | Interlocked.CompareExchange(ref value, updated, original)); 34 | } 35 | return updated; 36 | } 37 | 38 | T Swap_SimplerButLessEfficient(Func update) 39 | { 40 | T original, updated; 41 | do 42 | { 43 | original = value; 44 | updated = update(original); 45 | } 46 | while (original != 47 | Interlocked.CompareExchange(ref value, updated, original)); 48 | 49 | return updated; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /LaYumba.Functional/Coyo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | public class Coyo 6 | { 7 | public V Value { get; } 8 | public Func Func { get; } 9 | 10 | public Coyo(V value, Func func) 11 | { 12 | Value = value; 13 | Func = func; 14 | } 15 | } 16 | 17 | public static class Coyo 18 | { 19 | public static Coyo Of(V value) 20 | => new Coyo(value, x => (T)x); 21 | 22 | public static Coyo Map(this Coyo @this 23 | , Func func) 24 | => new Coyo(@this.Value, x => func(@this.Func(x))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LaYumba.Functional/Date.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | using static F; 6 | 7 | public static class Date 8 | { 9 | public static Option Parse(string s) 10 | { 11 | DateTime d; 12 | return DateTime.TryParse(s, out d) ? Some(d) : None; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LaYumba.Functional/Decimal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static LaYumba.Functional.F; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | public static class Decimal 7 | { 8 | public static Option Parse(string s) 9 | { 10 | decimal result; 11 | return decimal.TryParse(s, out result) 12 | ? Some(result) : None; 13 | } 14 | 15 | public static bool IsOdd(decimal i) => i % 2 == 1; 16 | 17 | public static bool IsEven(decimal i) => i % 2 == 0; 18 | 19 | public static new Func ToString = d => d.ToString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LaYumba.Functional/DictionaryExt.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public static class DictionaryExt 9 | { 10 | public static Option Lookup(this IDictionary dict, K key) 11 | { 12 | T value; 13 | return dict.TryGetValue(key, out value) ? Some(value) : None; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /LaYumba.Functional/Double.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | using static F; 4 | 5 | public static class Double 6 | { 7 | public static Option Parse(string s) 8 | { 9 | double result; 10 | return double.TryParse(s, out result) 11 | ? Some(result) : None; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LaYumba.Functional/EmptyList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | public class EmptyList : IEnumerable 7 | { 8 | IEnumerator IEnumerable.GetEnumerator() { yield break; } 9 | IEnumerator IEnumerable.GetEnumerator() { yield break; } 10 | } 11 | } -------------------------------------------------------------------------------- /LaYumba.Functional/Enum.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | using static F; 4 | 5 | public static class Enum 6 | { 7 | public static Option Parse(this string s) where T : struct 8 | => System.Enum.TryParse(s, out T t) ? Some(t) : None ; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LaYumba.Functional/Error.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | public static partial class F 4 | { 5 | public static Error Error(string message) => new Error(message); 6 | } 7 | 8 | public class Error 9 | { 10 | public virtual string Message { get; } 11 | public override string ToString() => Message; 12 | protected Error() { } 13 | internal Error(string Message) { this.Message = Message; } 14 | 15 | public static implicit operator Error(string m) => new Error(m); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LaYumba.Functional/Identity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | public static partial class F 6 | { 7 | public static Func Func(Func f) => f; 8 | 9 | public static Identity Identity(T value) => () => value; 10 | } 11 | 12 | // () -> T (aka. Identity) 13 | public static class FuncTExt 14 | { 15 | public static Func Map 16 | (this Func f, Func g) 17 | => () => g(f()); 18 | 19 | public static Func Bind 20 | (this Func f, Func> g) 21 | => () => g(f())(); 22 | 23 | // LINQ 24 | 25 | public static Func Select(this Func @this 26 | , Func func) => @this.Map(func); 27 | 28 | public static Func

SelectMany(this Func @this 29 | , Func> bind, Func project) 30 | => () => 31 | { 32 | T t = @this(); 33 | R r = bind(t)(); 34 | return project(t, r); 35 | }; 36 | } 37 | 38 | // same, with custom delegate 39 | 40 | public delegate T Identity(); 41 | 42 | public static class Identity 43 | { 44 | public static Identity Map(this Identity @this 45 | , Func func) => () => func(@this()); 46 | 47 | public static Identity Bind(this Identity @this 48 | , Func> func) => () => func(@this())(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LaYumba.Functional/Immutable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace LaYumba.Functional 6 | { 7 | public static class Immutable 8 | { 9 | public static T With(this T source, string propertyName, object newValue) 10 | where T : class 11 | { 12 | T @new = source.ShallowCopy(); 13 | 14 | typeof(T).GetBackingField(propertyName) 15 | .SetValue(@new, newValue); 16 | 17 | return @new; 18 | } 19 | 20 | public static T With(this T source, Expression> exp, object newValue) 21 | where T : class 22 | => source.With(exp.MemberName(), newValue); 23 | 24 | static string MemberName(this Expression> e) 25 | => ((MemberExpression)e.Body).Member.Name; 26 | 27 | static T ShallowCopy(this T source) 28 | => (T)source.GetType().GetTypeInfo().GetMethod("MemberwiseClone" 29 | , BindingFlags.Instance | BindingFlags.NonPublic) 30 | .Invoke(source, null); 31 | 32 | static string BackingFieldName(string propertyName) 33 | => string.Format("<{0}>k__BackingField", propertyName); 34 | 35 | static FieldInfo GetBackingField(this Type t, string propertyName) 36 | => t.GetTypeInfo().GetField(BackingFieldName(propertyName) 37 | , BindingFlags.Instance | BindingFlags.NonPublic); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LaYumba.Functional/Int.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | using static F; 4 | 5 | public static class Int 6 | { 7 | public static Option Parse(string s) 8 | { 9 | int result; 10 | return int.TryParse(s, out result) 11 | ? Some(result) : None; 12 | } 13 | 14 | public static bool IsOdd(int i) => i % 2 == 1; 15 | 16 | public static bool IsEven(int i) => i % 2 == 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LaYumba.Functional/LaYumba.Functional.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard1.6 4 | LaYumba.Functional 5 | 1.0.0 6 | https://github.com/la-yumba/functional-csharp-code 7 | https://github.com/la-yumba/functional-csharp-code/blob/master/LICENSE 8 | Enrico Buonanno 9 | A utility library for programming functionally in C# 10 | false 11 | Copyright 2017 (c) Enrico Buonanno. All rights reserved. 12 | functional programming 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /LaYumba.Functional/Long.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | using static F; 4 | 5 | public static class Long 6 | { 7 | public static Option Parse(string s) 8 | { 9 | long result; 10 | return long.TryParse(s, out result) 11 | ? Some(result) : None; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LaYumba.Functional/MonadStacks/TaskOptionMonad.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public static class TaskOptionMonad 9 | { 10 | public static Task> OrElse 11 | (this Task> task, Func>> fallback) 12 | => task.ContinueWith(t => 13 | t.Status == TaskStatus.Faulted 14 | ? fallback() 15 | : t.Result.Match( 16 | None: fallback, 17 | Some: val => Async(t.Result)) 18 | ) 19 | .Unwrap(); 20 | 21 | public static Task> Select 22 | (this Task> self 23 | , Func mapper) 24 | => self.Map(x => x.Map(mapper)); 25 | 26 | public static Task> SelectMany 27 | (this Task> task // Task> 28 | , Func>> bind) // -> (T -> Task>) 29 | => task.Bind(vt => vt.TraverseBind(bind)); 30 | //=> task.Bind(vt => vt.Traverse(bind).Map(vvr => vvr.Bind(vr => vr))); 31 | 32 | public static Task> SelectMany 33 | (this Task> task // Task> 34 | , Func>> bind // -> (T -> Task>) 35 | , Func project) 36 | => task 37 | .Map(vt => vt.TraverseBind(t => bind(t).Map(vr => vr.Map(r => project(t, r))))) 38 | .Unwrap(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LaYumba.Functional/NoneType.cs: -------------------------------------------------------------------------------- 1 | namespace LaYumba.Functional 2 | { 3 | public struct NoneType 4 | { 5 | public static readonly NoneType Default = new NoneType(); 6 | } 7 | } -------------------------------------------------------------------------------- /LaYumba.Functional/ObservableExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using LaYumba.Functional; 4 | using static LaYumba.Functional.F; 5 | 6 | using System.Reactive.Linq; 7 | 8 | namespace LaYumba.Functional 9 | { 10 | public static class ObservableExt 11 | { 12 | // Safely performs a Task-returning function for each t in ts, 13 | // and returns a stream of results for the completed tasks, 14 | // and a stream of exceptions 15 | public static (IObservable Completed, IObservable Faulted) 16 | Safely(this IObservable ts, Func> f) 17 | => ts 18 | .SelectMany(t => 19 | Observable.FromAsync(() => 20 | f(t).Map( 21 | Faulted: ex => ex, 22 | Completed: r => Exceptional(r)))) 23 | .Partition(); 24 | 25 | public static (IObservable Successes, IObservable Exceptions) 26 | Partition(this IObservable> excTs) 27 | { 28 | bool IsSuccess(Exceptional ex) 29 | => ex.Match(_ => false, _ => true); 30 | 31 | T ValueOrDefault(Exceptional ex) 32 | => ex.Match(exc => default(T), t => t); 33 | 34 | Exception ExceptionOrDefault(Exceptional ex) 35 | => ex.Match(exc => exc, _ => default(Exception)); 36 | 37 | return (excTs.Where(IsSuccess).Select(ValueOrDefault) 38 | , excTs.Where(e => !IsSuccess(e)).Select(ExceptionOrDefault)); 39 | } 40 | 41 | public static (IObservable Passed, IObservable Failed) Partition 42 | (this IObservable source, Func predicate) 43 | => (Passed: (from t in source where predicate(t) select t) 44 | , Failed: (from t in source where !predicate(t) select t)); 45 | 46 | public static (IObservable Passed, IObservable Failed) Partition 47 | (this IObservable source 48 | , Func If 49 | , Func Then 50 | , Func Else) 51 | => (source.Where(t => If(t)).Select(Then) 52 | , source.Where(t => !If(t)).Select(Else)); 53 | 54 | public static IObservable<(T Previous, T Current)> 55 | PairWithPrevious(this IObservable source) 56 | => source 57 | .Scan((Previous: default(T), Current: default(T)) 58 | , (prev, current) => (prev.Current, current)) 59 | .Skip(1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /LaYumba.Functional/Pattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace LaYumba.Functional 8 | { 9 | using static F; 10 | 11 | public class NonExhaustivePattern : Exception { } 12 | 13 | public class Pattern : Pattern { } 14 | 15 | public class Pattern : IEnumerable 16 | { 17 | IList<(Type, Func)> funcs = new List<(Type, Func)>(); 18 | 19 | IEnumerator IEnumerable.GetEnumerator() => funcs.GetEnumerator(); 20 | 21 | public void Add(Func func) 22 | => funcs.Add((typeof(T), o => func((T)o))); 23 | 24 | public Pattern Default(Func func) 25 | { 26 | Add((object _) => func()); 27 | return this; 28 | } 29 | 30 | public Pattern Default(R val) 31 | { 32 | Add((object _) => val); 33 | return this; 34 | } 35 | 36 | public R Match(object value) 37 | { 38 | Func matchingDel = null; 39 | try 40 | { 41 | matchingDel = funcs.First(InputArgMatchesTypeOf(value)).Item2; 42 | } 43 | catch(InvalidOperationException) 44 | { 45 | throw new NonExhaustivePattern(); 46 | } 47 | 48 | return matchingDel(value); 49 | } 50 | 51 | static Func<(Type, Func), bool> InputArgMatchesTypeOf(object value) 52 | => tup => tup.Item1.GetTypeInfo().IsAssignableFrom(value.GetType()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /LaYumba.Functional/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("47186ea7-3657-429b-8ff7-c1b7974868ed")] 12 | 13 | [assembly: InternalsVisibleTo("LaYumba.Functional.Tests")] -------------------------------------------------------------------------------- /LaYumba.Functional/Reader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | public delegate T Reader(Env env); 6 | 7 | public static class Reader 8 | { 9 | // standard monad methods 10 | 11 | public static Reader Map 12 | (this Reader f, Func g) 13 | => x => g(f(x)); 14 | 15 | public static Reader Bind 16 | (this Reader f, Func> g) 17 | => env => g(f(env))(env); 18 | 19 | public static Reader Bind 20 | (this Reader f, Func g) 21 | => env => g(f(env), env); 22 | 23 | // Reader specific 24 | 25 | public static Reader Ask() => env => env; 26 | 27 | // LINQ 28 | 29 | public static Reader Select(this Reader @this 30 | , Func func) => @this.Map(func); 31 | 32 | public static Reader SelectMany(this Reader @this 33 | , Func> bind, Func project) 34 | => env => 35 | { 36 | var t = @this(env); 37 | var r = bind(t)(env); 38 | return project(t, r); 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LaYumba.Functional/StatefulComputation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Unit = System.ValueTuple; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public delegate (T Value, S State) StatefulComputation(S state); 9 | 10 | public static class StatefulComputation 11 | { 12 | public static StatefulComputation Return(T value) 13 | => state => (value, state); 14 | 15 | public static StatefulComputation Put(S newState) 16 | => state => (Unit(), newState); 17 | 18 | public static StatefulComputation Get 19 | => state => (state, state); 20 | } 21 | 22 | public static class StatefulComputation 23 | { 24 | public static T Run(this StatefulComputation f, S state) 25 | => f(state).Value; 26 | 27 | public static StatefulComputation Put(S newState) 28 | => state => (Unit(), newState); 29 | 30 | public static StatefulComputation Get() 31 | => state => (state, state); 32 | 33 | public static StatefulComputation Select 34 | (this StatefulComputation f, Func project) 35 | => state0 => 36 | { 37 | var(t, state1) = f(state0); 38 | return (project(t), state1); 39 | }; 40 | 41 | public static StatefulComputation SelectMany 42 | (this StatefulComputation StatefulComputation, Func> f) 43 | => state0 => 44 | { 45 | var(t, state1) = StatefulComputation(state0); 46 | return f(t)(state1); 47 | }; 48 | 49 | public static StatefulComputation SelectMany 50 | (this StatefulComputation f 51 | , Func> bind 52 | , Func project) 53 | => state0 => 54 | { 55 | var(t, state1) = f(state0); 56 | var(r, state2) = bind(t)(state1); 57 | var rr = project(t, r); 58 | return (rr, state2); 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /LaYumba.Functional/String.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | public static class String 6 | { 7 | public static Func Trim = s => s.Trim(); 8 | public static Func ToLower = s => s.ToLower(); 9 | public static Func ToUpper = s => s.ToUpper(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LaYumba.Functional/Traversable/Option.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public static class OptionTraversable 9 | { 10 | // Exceptional 11 | public static Exceptional> Traverse 12 | (this Option tr, Func> f) 13 | => tr.Match( 14 | None: () => Exceptional((Option)None), 15 | Some: t => f(t).Map(Some) 16 | ); 17 | 18 | // Task 19 | public static Task> Traverse 20 | (this Option @this, Func> func) 21 | => @this.Match( 22 | None: () => Async((Option)None), 23 | Some: t => func(t).Map(Some) 24 | ); 25 | 26 | public static Task> TraverseBind(this Option @this 27 | , Func>> func) 28 | => @this.Match( 29 | None: () => Async((Option)None), 30 | Some: t => func(t) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LaYumba.Functional/Traversable/Validation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | using static F; 7 | 8 | public static class ValidationTraversable 9 | { 10 | // Exceptional 11 | public static Exceptional> Traverse 12 | (this Validation tr, Func> f) 13 | => tr.Match( 14 | Invalid: reasons => Exceptional(Invalid(reasons)), 15 | Valid: t => f(t).Map(Valid) 16 | ); 17 | 18 | // Task 19 | public static Task> Traverse 20 | (this Validation @this, Func> func) 21 | => @this.Match( 22 | Invalid: reasons => Async(Invalid(reasons)), 23 | Valid: t => func(t).Map(Valid) 24 | ); 25 | 26 | public static Task> TraverseBind(this Validation @this 27 | , Func>> func) 28 | => @this.Match( 29 | Invalid: reasons => Async(Invalid(reasons)), 30 | Valid: t => func(t) 31 | ); 32 | } 33 | 34 | public static class TaskTraversable 35 | { 36 | public static Validation> Traverse(this Task @this 37 | , Func> func) 38 | { throw new NotImplementedException(); } 39 | } 40 | 41 | public static class ExceptionalTraversable 42 | { 43 | public static Validation> Traverse 44 | (this Exceptional tr, Func> f) 45 | => tr.Match( 46 | Exception: e => Valid((Exceptional)e), 47 | Success: t => from r in f(t) select Exceptional(r)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LaYumba.Functional/Try.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static LaYumba.Functional.F; 3 | 4 | namespace LaYumba.Functional 5 | { 6 | public delegate Exceptional Try(); 7 | 8 | public static partial class F 9 | { 10 | public static Try Try(Func f) => () => f(); 11 | } 12 | 13 | public static class TryExt 14 | { 15 | public static Exceptional Run(this Try @try) 16 | { 17 | try { return @try(); } 18 | catch (Exception ex) { return ex; } 19 | } 20 | 21 | public static Try Map 22 | (this Try @try, Func f) 23 | => () 24 | => @try.Run() 25 | .Match>( 26 | ex => ex, 27 | t => f(t)); 28 | 29 | public static Try> Map 30 | (this Try @try, Func func) 31 | => @try.Map(func.Curry()); 32 | 33 | public static Try Bind 34 | (this Try @try, Func> f) 35 | => () 36 | => @try.Run().Match( 37 | Exception: ex => ex, 38 | Success: t => f(t).Run()); 39 | 40 | // LINQ 41 | 42 | public static Try Select(this Try @this, Func func) => @this.Map(func); 43 | 44 | public static Try SelectMany 45 | (this Try @try, Func> bind, Func project) 46 | => () 47 | => @try.Run().Match( 48 | ex => ex, 49 | t => bind(t).Run() 50 | .Match>( 51 | ex => ex, 52 | r => project(t, r)) 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /LaYumba.Functional/TupleExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LaYumba.Functional 4 | { 5 | public static class TupleExt 6 | { 7 | public static R Match(this Tuple @this 8 | , Func func) => func(@this.Item1, @this.Item2); 9 | } 10 | } -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/la-yumba/functional-csharp-code/3877328c006fb2dc1109769884ae042cf4bd9c49/cover.jpg -------------------------------------------------------------------------------- /setupRepl.csx: -------------------------------------------------------------------------------- 1 | //#r "..\..\LaYumba.Functional\bin\Debug\netstandard1.6\LaYumba.Functional.dll" 2 | #r "LaYumba.Functional\bin\Debug\netstandard1.6\LaYumba.Functional.dll" 3 | #r "LaYumba.Functional.Data\bin\Debug\netstandard1.6\LaYumba.Functional.Data.dll" 4 | 5 | 6 | using LaYumba.Functional; 7 | using static LaYumba.Functional.F; 8 | using static System.Console; 9 | 10 | var daysOfWeek = Enum.GetValues(typeof(DayOfWeek)).Cast().Select(d => d.ToString()); 11 | 12 | static Func multiply = (i, j) => i * j; 13 | static Func @double = i => i * 2; 14 | --------------------------------------------------------------------------------