:::info
Functional Tools and Principles for LoB Apps
:::
Note:
- Title not Functional programming
- not a singluar concept
- it is a collection of tools and principles
- the barrier of entry is very low
- independently
- at any place
- at any time
- by any amount
- LoB Apps are apps that aremainly constrained by business logic complexity
- Function tools and principles are great for reducing code complexity
---
:::info
Why functional tools and principles
:::
- Easy to understand
- Less artificial complexity
- Easy to test
- Bug free code
- High productivity
- Low barrier of entry
Note:
- demonstrate with examples
- except bug free code can not be
- demonstrated with examples
- proven with reasoning
- testimonies of many others and also my experiences
---
:::info
Write pure functions
:::
```c#
void impure_divide()
{
var a = readA();
var b = readB();
var c = a / b;
saveC(c);
}
```
Note:
- overly simplified example: division
- to make the point obvious
- Why is this function not pure
- void
- read db
- write to db
- business log code should be pure
- extract a pure function to
- test
- reuse
----
**pure function**
```c#
int divide(int a, int b) => a / b;
```
mental model of pure functions
```c#
(input1, input2, ...) => (output1, output2, ...)
```
Note:
- why is this pure
- all data from input
- all result to output
- does not mutate input
- no side effect
- whether to create real types for the input and output
- depend on whether it makes business sense
- try not to polute type space with classes that do not carry significant meaning
- not strictly pure, but more on this later
---
:::info
Push IO to the edge
:::
```c#
var inputModel = Query.ReadData();
var outputModel = BusinessLogic.Calculate(inputModel)
var _ = Repository.Save(outputModel);
```
Note: same thing as Write pure function
- it is just a pratically ways of writting pure function while still doing IO which is necessary in real applications
----
**A fancier version of the code above with marginal benefit**
```c#
var _ = IOMonad
.ReadData()
.Select(BusinessLogic.Calculate)
.Select(Repository.SaveResult)
.Evaluate()
```
----
This is the functional way of implementing
- **Domain Driven**
- **Onion Architecture**
- **Clean Architecture**

Note:
- domain code should ignore IO
- not injecting IO interfaces to domain code
---
:::info
Write immutable code
:::
```c#
var aggregate = new Aggregate();
aggregate.AssignA(a);
aggregate.AssignB(b);
var c = aggregate.DivideResult();
aggregate.Save();
```
Note:
- hard to read
- logic coded in aggregate hard to reuse
- e.g. interest calculation:
- assign rate
- assign amount
- assign duration
- asks for interest
----
**mutable code in the real world**
```c#
var aggregate = new Aggregate();
...
service1.Foo(aggregate)
...
service2.Bar(aggregate);
...
var c = aggregate.DivideResult();
...
aggregate.Save();
```
Note: imaging those four lines are
- in different files
- embed among other code
- executed in different threads
----
**immutable code**
```c#
var c = a / b;
```
---
:::info
Think in types
:::
- C# classes can be
- Actors
- internal states
- state mutating behaviours
- Subset of real-world values
- data that satisfy constraints
Note: two fundamentally different views of what c# classes are
- both fit for different applications
- the actor view fits applications that maintain state in memory
- single-player games, decktop apps without a DB backend
- the data view fits applications that maintain state separate from their running code
- the code becomes stateless data processors
----
- Example types in C#:
- ```object``` represents all real world values
- ```int``` represents a subset of realworld values
- number
- interger
- Min/Max
- ```uint``` represents another subset of number values
- ```NonZeroInt``` is another subset of int
- ```String```, ```UserEnteredEmailAddress```, ```VerifiedEmailAddress```
- ```Loan, SubventionLoan, RetailLoan, RetailPersonalLoan```, …
Note: data that satisfy certain constraint/validation
----
**A Web app made up of collaborating actors**

[Image credit to Scott Wlaschin](https://www.slideshare.net/ScottWlaschin/reinventing-the-transaction-script-ndc-london-2020)
----
**A Web app made up of functions**

Note:
- the difference is in complexity.
- Functions calling each other are easier to understand than actors calling each other
---
:::info
Functional error handling
:::
```c#
T ReadObjectFromDB<T>(int id, IQueryable<T> tbl)
```
Note: problem with this code
- what if id is invalid
- error cases are not specified
- error handling is not enforced
- a source of NullReferenceException
- problem is not return null
- problem is return null without telling the compiler/consumer
----
```c#
T ReadObjectFromDB<T>(int id, IQueryable<T> tbl)
=> tbl.First(x=>x.Id==id);
```
```c#
T ReadObjectFromDB<T>(int id, IQueryable<T> tbl)
{
var t = tbl.FirstOrDefault(x=>x.Id==id);
if(t==null)
{
throw new InvalidOperationException("t is null")
}
return t;
}
```
Note: problem with this code
- the 2 implementation are the same
- mixing system exceptions with business logic exceptions
- impossible for caller to handle it meaningfully
- handling exception is not enforced by compiler
----
**declare error cases to the caller and the compiler**
```c#
T? ReadObject<T>(int id, IQueryable<T> tbl)
```
```c#
Option<T> ReadObject<T>(int id, IQueryable<T> tbl)
```
```c#
Exception<T> ReadObject<T>(int id, IQueryable<T> tbl)
```
```c#
Either<T, Exception> ReadObject<T>(int id, IQueryable<T> tbl)
```
```c#
(T | Exception) ReadObject<T>(int id, IQueryable<T> tbl)
```
Note:
- distinguish between businesslogic and system exceptions
- tell compiler about the error case
- will uncover business cases
- divide by zero,
- null column in db
- missing data in db,
- invalid input data
- how to handle business logic exceptions is often case by case
- controller command handler vs event consumer
- borrower/lender vs staff user
- global handler is good for system exceptions
- invalid user input vs invalid internal data
----
**anything wrong with this function?**
```c#
int divide(int a, int b) => a / b;
```
Note:
- it doesn't always return int
- mixing exception types
----
**functions with different levels of purity**
```c#
int pure_divide(int a, int b)
{
if(b==0) ...;
return a/b;
}
```
```c#
Exception<int> pure_divide(int a, int b)
{
if(b==0) ...;
return a/b;
}
```
Note: what to do when b==0
- throw domain specific exception with error that user(staff, eng) understand
- watchout for exception throwing BCL calls
- IEnumerable.First, IQueryable.Sum, IDictionary[]
----
```c#
int pure_divide(int a, NonZeroInt b) => return a/b;
```
Note: leave what to do to the caller
- leave decision to the user
- no need to aim for 100% purity
- our aim is code simplicity, not mathematically proven code
- but make sure business logic error handling is enforced
---
:::info
Unit of reuse should be functions
:::
- Interfaces are for polymorphic entities
- Service functions are stateless and independent
- Interfaces for a group of independent functions do not make sense
- Interfaces for functions are their signatures
----
- **Reuse by interface**
- **Hidden dependency**
- **Mocking in unit test**
```c#
public interface IHolidayService
{
public bool IsHoliday(DateTime date);
}
public class HolidayService : IHolidayService
{
public bool IsHoliday(DateTime date) => false;
}
public class BusinessDayCalculationService
{
private readonly IHolidayService _holidayService;
public BusinessDayCalculationService(IHolidayService holidayService)
{
_holidayService = holidayService;
}
public DateTime? NthBusinessDay(int year, int month, int n)
{
int cur = 0;
var bom = new DateTime(year, month, 1);
var eom = new DateTime(year, month, DateTime.DaysInMonth(year, month));
for (var date = bom; date <= eom; date = date.AddDays(1))
{
if (!_holidayService.IsHoliday(date)) cur++;
if (cur == n) return date;
}
return null;
}
}
```
----
**reuse by function**
```c#
public static class BusinessDayCalculator
{
public static DateTime? NthBusinessDay(int year, int month, int n, Func<DateTime, bool> isBDay)
=> new DateTime(year, month, 1)
.DaysInRange(new DateTime(year, month, DateTime.DaysInMonth(year, month)))
.Where(isBDay)
.Skip(n - 1)
.FirstOrNull();
}
```
Note:
- composition of small functions
- Ultimate SPR
----
:::info
Testing of pure functions is easy
:::
```c#
[Test]
[TestCase(2022, 4, 1, "2022-4-1")]
[TestCase(2022, 4, 2, "2022-4-4")]
[TestCase(2022, 4, 32, "")]
public void NthBusinessDay_Tests(int year, int month, int n, string expectedDateStr)
{
Func<DateTime, bool> isBDay = date => date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday;
var result = BusinessDayCalculator.NthBusinessDay(year, month, n, isBDay);
var expectedDate = expectedDateStr.ToDateTime();
Assert.AreEqual(expectedDate, result);
}
```
Note:
- no test setup
- no mocking
---
:::info
C# has decent support for functional programming
:::
- lambda makes functions first class
- static functions should be the default
- records have value semantics and are immutable
- LINQ for composing functions
- value tuple and generics for composing types
Note:
- value symantics means data is data
----
:::info
C# missing features
:::
- discriminated union
```type UserAnswer = "yes" | "no"```
- final initializer
```var newRcd = rcd with { Prop1 = "new value" } ```
- pipe operator
```var a = b |> func1 |> func2```
Note:
- discriminated union
- final initializer, no easy way of validating newRecord
- `Valid<T>`
---
# Thanks
Questions?
{"metaMigratedAt":"2023-06-16T22:42:14.997Z","metaMigratedFrom":"YAML","title":"Thanks","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"showNotes\":false,\"spotlight\":{\"enabled\":true}}","contributors":"[{\"id\":\"ebf2559a-7f75-44f9-b53b-65ad0b4ff14c\",\"add\":22379,\"del\":26197}]"}