Skip to content

Instantly share code, notes, and snippets.

@natalie-o-perret
Last active November 13, 2019 15:56
Show Gist options
  • Save natalie-o-perret/da8849156570fa6421e5a872307d6ec5 to your computer and use it in GitHub Desktop.
Save natalie-o-perret/da8849156570fa6421e5a872307d6ec5 to your computer and use it in GitHub Desktop.
Zoran Horvat Principles
using System;
using System.Linq;
using System.Collections.Generic;
namespace ZoranHorvat
{
public static class FunctionalCSharp
{
public static void Main(params string[] args)
{
// Need to include some usecases here below...
}
}
public class Option<T>
{
/// <summary>
/// Adapter: T -> Option<T>
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static implicit operator Option<T>(T value) =>
new Some<T>(value);
/// <summary>
/// Adapter: None -> Option<T>
/// </summary>
/// <param name="none"></param>
/// <returns></returns>
public static implicit operator Option<T>(None none) =>
new None<T>();
}
public class Some<T> : Option<T>
{
private readonly T _content;
public Some(T content)
{
_content = content;
}
/// <summary>
/// Adapter: Some<T> -> T
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static implicit operator T(Some<T> value) => value._content;
}
public class None<T> : Option<T>
{
}
public class None
{
public static None Value { get; } = new None();
private None(){}
}
public static class OptionAdapters
{
/// <summary>
/// Adapter: Option<T> -> Option<TResult>
/// </summary>
/// <param name="option"></param>
/// <param name="map"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
public static Option<TResult> Map<T, TResult>(this Option<T> option, Func<T, TResult> map) =>
option is Some<T> some ?
(Option<TResult>) map(some) :
None.Value;
/// <summary>
/// Adapter: T -> Option<T>
/// </summary>
/// <param name="source"></param>
/// <param name="predicate"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Option<T> When<T>(this T source, Func<T, bool> predicate) =>
predicate(source) ?
(Option<T>) source :
None.Value;
/// <summary>
/// Adapter: (Option<T>, T) -> T
/// </summary>
/// <param name="option"></param>
/// <param name="whenNone"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Reduce<T>(this Option<T> option, T whenNone) =>
option is Some<T> some ?
(T) some :
whenNone;
/// <summary>
/// Adapter: (Option<T>, T) -> T (lazy variant)
/// </summary>
/// <param name="option"></param>
/// <param name="whenNone"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Reduce<T>(this Option<T> option, Func<T> whenNone) =>
option is Some<T> some ?
(T) some :
whenNone();
}
public static class CompositionExtensions
{
public static TResult Map<T, TResult>(this T source, Func<T, TResult> map) =>
map(source);
}
public static class EnumerableExtensions
{
public static IEnumerable<TResult> Flatten<T, TResult>(this IEnumerable<T> source, Func<T, Option<TResult>> map) =>
source
.Select(map)
.OfType<Some<TResult>>()
.Select(x => (TResult) x);
public static Option<T> FirstOrNone<T>(this IEnumerable<T> source, Func<T, bool> predicate) =>
source.Where(predicate)
.Select<T, Option<T>>(x => x)
.DefaultIfEmpty(None.Value)
.First();
}
public abstract class Either<TLeft, TRight>
{
public static implicit operator Either<TLeft, TRight>(TLeft left) =>
new Left<TLeft, TRight>(left);
public static implicit operator Either<TLeft, TRight>(TRight right) =>
new Right<TLeft, TRight>(right);
}
public class Left<TLeft, TRight> : Either<TLeft, TRight>
{
private readonly TLeft _content;
public Left(TLeft content)
{
_content = content;
}
public static implicit operator TLeft(Left<TLeft, TRight> left) => left._content;
}
public class Right<TLeft, TRight> : Either<TLeft, TRight>
{
private readonly TRight _content;
public Right(TRight content)
{
_content = content;
}
public static implicit operator TRight(Right<TLeft, TRight> right) => right._content;
}
public static class EitherAdapters
{
public static Either<TLeft, TNewRight> Map<TLeft, TRight, TNewRight>(this Either<TLeft, TRight> either, Func<TRight, TNewRight> map) =>
either is Right<TLeft, TRight> right
? (Either<TLeft, TNewRight>)map(right)
: (TLeft)(Left<TLeft, TRight>)either;
public static Either<TLeft, TNewRight> Map<TLeft, TRight, TNewRight> (this Either<TLeft, TRight> either, Func<TRight, Either<TLeft, TNewRight>> map) =>
either is Right<TLeft, TRight> right
? map(right)
: (TLeft)(Left<TLeft, TRight>)either;
public static TRight Reduce<TLeft, TRight>(this Either<TLeft, TRight> either, Func<TLeft, TRight> map) =>
either is Left<TLeft, TRight> left
? map(left)
: (Right<TLeft, TRight>) either;
}
}

Functional Programming

  • HOFs: Higher-Order Functions, receives a function, returns a function or both.
  • Pure Function: No side effets, same result for same argument values
  • Lazy Evaluation: Compute functions only when needed
  • Pattern Matching: Match object against patterns as flow control
  • Value Type: Immutable, what they ARE is their identity
  • Referential Transparency: Expression can be replaced with its value

Programming into a language

But C# is still known to be an OO language, thru and thu... however,

There is the way to include functional programming concepts into C# without damaging the design.

Objects remain primary building blocks while object interfaces made more functional

Drawbacks of Object Design

  • Methods know too much: 1 method is typically responsible for an entire complex operation
  • Hard to manage in large projects: complex dependencies are an obstacle in large code bases
  • Polymorphism: increase complexity of objects, it's a liability, makes it harder to develop rich models

=> Solution includes functional concepts: model remains OO, with more functional design of classes

  • Classes remain
  • Operations chopped into classes
  • Combine simple functions
  • Build larger features out of them

Applying functional design to classes

  • Make public methods simple
  • Make then isolated
  • Do not expose complex functions
  • Let the consumer compose with small functions

=> It's not all about lambdas and HOFs ;-)

OO underlying issue: it tackles the real-world complexity but leaves a fundamental interconnectedness of all things which is another layer of complexity.

ValueTuple is not goood a choice for public APIs

  • It's a struct
    • Good: lower runtime memory footprint
    • Bad: your consumer is gonna be stuck with that
  • Components in ValueTuple are public mutable fields (also get stuck with your consumer...)

Pure Functions

  • No observable side effects

    • Returned value only depends on arguments
    • Referentially transparent
  • When defined on objects:

    • Classes must be immutable aka value objects:
      • Note: It's not a .NET value type
      • Can be used like a primitive value
      • Can be used an index key
      • Semantics:
        • Must override Equals() and GetHashCode()
        • Advise to overload == and != operators
        • Advise to also implement IEquatable<T>
        • Value class is sealed
    • Arguments and all components must also be immutable
  • Supports Memoization

    • Balance between reduced number of calls and cache hit
    • Good for DP problems (ie. reusing previous results as part breaking a problem into smaller ones).
    • Remember DP is a generalization of Memoization
    • Think about PostSharp to implement memoization out of the box
    • Screaming Example: cache results for Fibonacci to reduce the number of calls required to perform computations to 1
  • Benefits

    • Same result for the same object and arguments
    • Simplify execution model
    • Saves us from bugs

C# 7 Pattern Matching

  • Functional-style
  • Capturing strongly typed variables
  • Type Matching Expressions (syntax: if, switch [+ when] & ternary)

Extension Methods

  • Keep behaviour separate from data
  • Enclose extensions in a separate C# namespace
  • Chaining them (composition), with a rule of thumb: if code not obviously correct then its design is not good, better than one big method, no place left for bugs

Railway-oriented Programming

  • Execution progresses over one track or the other
  • Regular tracks may contain an object
  • Switch to alternate track when the object ceases to exist
  • Defensive Coding: both tracks must be defined
  • Applied to both:
    • Option<T> which can be a value (ie. Some<T>) or lack of thereof (ie. None<T>)
    • Either<TLeft, TRight> which can be an error (ie. Left<TLeft, Right>) or a proper value (ie. Right<TLeft, Right>)

Discriminated Unions

HOFs

Partial Functions and Closures

Value Objects

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment