Functional concepts in C#

85
Functional Concepts in C# Or “Who the F# Wrote This?” https://github.com/mrdrbob/sd-code-camp-2016

Transcript of Functional concepts in C#

Page 1: Functional concepts in C#

Functional Concepts in C#

Or “Who the F# Wrote This?”

https://github.com/mrdrbob/sd-code-camp-2016

Page 2: Functional concepts in C#

Thanks Sponsors!

Page 3: Functional concepts in C#

Let’s Manage Expectations!

Page 4: Functional concepts in C#

What this talk is

A gentle introduction to functional paradigms using a language you may already be familiar with.

A comparison between OOP and functional styles

A discussion on language expectations

Page 5: Functional concepts in C#

What this talk isn’t

“OOP is dead!”

“Functional all the things!”

“All code should look exactly like this!” (Spoiler: it probably shouldn’t)

Page 6: Functional concepts in C#

Who I am

Bob Davidson

C# / Web Developer 11 years

Blend Interactive

A guy who is generally interested in and learning about functional programming concepts

https://github.com/mrdrbob

Page 7: Functional concepts in C#

Who I am Not

A functional programming expert who says things like:

“All told, a monad in X is just a monoid in the category of endofunctors of X, with product ×replaced by composition of endofunctors and unit set by the identity endofunctor.”-Saunders Mac Lane

Page 8: Functional concepts in C#

Let’s Build a Parser!

A highly simplified JSON-like syntax for strings and integers.

Page 9: Functional concepts in C#

IntegersOne or more digits

Page 10: Functional concepts in C#

StringsStarts & ends with double quote.Quotes can be escaped with slash.Slash can be escaped with slash.

Can be empty.

Page 11: Functional concepts in C#

Iteration 1.0

Page 12: Functional concepts in C#

The IParser<TValue> Interface

public interface IParser<TValue>  {

bool  TryParse(string raw,  out TValue value);

}

Page 13: Functional concepts in C#

IntegerParserpublic class IntegerParser :  IParser<int>  {

public bool  TryParse(string raw,  out int value)  {

value  = 0;

int x  = 0;

List<char> buffer  = new List<char>();

while (x  < raw.Length && char.IsDigit(raw[x]))  {

buffer.Add(raw[x]);

x  += 1;

}

if (x  == 0)

return false;

//  Deal  with  it.

value  = int.Parse(new string(buffer.ToArray()));

return true;

}

}

Page 14: Functional concepts in C#

IntegerParserpublic class IntegerParser :  IParser<int>  {

public bool  TryParse(string raw,  out int value)  {

value  = 0;

int x  = 0;

List<char> buffer  = new List<char>();

while (x  < raw.Length && char.IsDigit(raw[x]))  {

buffer.Add(raw[x]);

x  += 1;

}

if (x  == 0)

return false;

value  = int.Parse(new string(buffer.ToArray()));

return true;

}

}

Page 15: Functional concepts in C#

StringParserpublic class StringParser :  IParser<string>  {

public bool  TryParse(string raw,  out string value)  {value  = null;

int x  = 0;if (x  == raw.Length || raw[x]  != '"')

return false;

x  += 1;

List<char> buffer  = new List<char>();while (x  < raw.Length && raw[x]  != '"')  {

if (raw[x]  == '\\')  {x  += 1;if (x  == raw.Length)

return false;

if (raw[x]  == '\\')buffer.Add(raw[x]);

else if (raw[x]  == '"')buffer.Add(raw[x]);

elsereturn false;

}  else {buffer.Add(raw[x]);

}

x  += 1;}

if (x  == raw.Length)return false;

x  += 1;value  = new string(buffer.ToArray());return true;

}}

Page 16: Functional concepts in C#

Possible Issues

public class StringParser :  IParser<string>  {public bool  TryParse(string raw,  out string value)  {

value  = null;

int x  = 0;if (x  == raw.Length || raw[x]  != '"')

return false;

x  += 1;

List<char> buffer  = new List<char>();while (x  < raw.Length && raw[x]  != '"')  {

if (raw[x]  == '\\')  {x  += 1;if (x  == raw.Length)

return false;

if (raw[x]  == '\\')buffer.Add(raw[x]);

else if (raw[x]  == '"')buffer.Add(raw[x]);

elsereturn false;

}  else {buffer.Add(raw[x]);

}

x  += 1;}

if (x  == raw.Length)return false;

x  += 1;value  = new string(buffer.ToArray());return true;

}}

Repeated checks against running out of input

Easily missed logic for moving input forward

No way to see how much input was consumed / how much is left

Hard to understand at a glance what is happening

public class IntegerParser :  IParser<int>  {

public bool  TryParse(string raw,  out int value)  {

value  = 0;

int x  = 0;

List<char> buffer  = new List<char>();

while (x  < raw.Length && char.IsDigit(raw[x]))  {

buffer.Add(raw[x]);

x  += 1;

}

if (x  == 0)

return false;

//  Deal  with  it.

value  = int.Parse(new string(buffer.ToArray()));

return true;

}

}

Page 17: Functional concepts in C#

Rethinking the ParserMake a little more generic / reusable

Break the process down into a series of rules which can be composed to make new parsers from existing parsers

Build a framework that doesn’t rely on strings, but rather a stream of tokens

Page 18: Functional concepts in C#

Iteration 2.0

Composition

Page 19: Functional concepts in C#

[Picture of Legos Here]

Page 20: Functional concepts in C#

One or More Times

A Parser Built on Rules (Integer Parser)

[0-9]

Page 21: Functional concepts in C#

Ignore Latter

Keep Latter

Zero or More Times

Any of these

NotKeep Latter

A Parser Built on Rules (String Parser)

\ “

Keep Latter

\ \

Any of these

\ “

Page 22: Functional concepts in C#

A Set of Rules

Match QuoteMatch SlashMatch Digit

Match Then KeepMatch Then IgnoreMatch AnyMatch Zero or More TimesMatch One or More TimesNot

Page 23: Functional concepts in C#

Rethinking the SourceHandle tokens other than chars (such as byte streams, pre-lexed tokens, etc)

Need the ability to continue parsing after a success

Need the ability to reset after a failure

Page 24: Functional concepts in C#

Rethinking the Sourcepublic interface ISource<Token>  {

Token Current {  get;  }

bool  HasMore {  get;  }

int CurrentIndex {  get;  }

void Move(int index);

}

public class StringSource :  ISource<char>  {

readonly  string  value;

int index;

public StringSource(string value)  {  this.value  = value;  }

public char Current  => value[index];

public int CurrentIndex  => index;

public bool  HasMore  => index  < value.Length;

public void Move(int index)  =>  this.index  =  index;

}

Page 25: Functional concepts in C#

Creating a Rulepublic interface IRule<Token,  TResult>  {

bool  TryParse(ISource<Token> source,  out TResult result);

}

Page 26: Functional concepts in C#

Char Matches...public class CharIsQuote :  IRule<char,  char>  {

public bool  TryParse(ISource<char> source,  outchar result)  {

result  = default(char);if (!source.HasMore)

return false;if (source.Current != '"')

return false;result  = source.Current;source.Move(source.CurrentIndex + 1);return true;

}}

public class CharIs :  IRule<char,  char>  {readonly  char toMatch;public CharIs(char toMatch)  {  this.toMatch  =

toMatch;  }public bool  TryParse(ISource<char> source,  out char

result)  {result  = default(char);if (!source.HasMore)

return false;if (source.Current != toMatch)

return false;result  = source.Current;source.Move(source.CurrentIndex + 1);return true;

}}

Page 27: Functional concepts in C#

Char Matches...public abstract class CharMatches :  IRule<char,  char>  {

protected abstract bool  IsCharMatch(char c);

public bool  TryParse(ISource<char> source,  out char result)  {result  = default(char);if (!source.HasMore)

return false;if (!IsCharMatch(source.Current))

return false;result  = source.Current;source.Move(source.CurrentIndex + 1);return true;

}}

public class CharIsDigit :  CharMatches  {protected override  bool  IsCharMatch(char c)  =>  char.IsDigit(c);

}

public class CharIs :  CharMatches  {

readonly  char toMatch;

public CharIs(char toMatch)  {  this.toMatch  = toMatch;  }

protected override  bool  IsCharMatch(char c)  =>  c  ==  toMatch;

}

Page 28: Functional concepts in C#

First Match (or Any)public class FirstMatch<Token,  TResult>  :  IRule<Token,  TResult>  {

readonly  IRule<Token,  TResult>[]  rules;public FirstMatch(IRule<Token,  TResult>[]  rules)  {  this.rules  = rules;  }

public bool  TryParse(ISource<Token> source,  out TResult result)  {foreach(var  rule  in  rules)  {

int originalIndex  = source.CurrentIndex;if (rule.TryParse(source,  out  result))

return true;source.Move(originalIndex);

}

result  = default(TResult);return false;

}}

Page 29: Functional concepts in C#

Match Then... public abstract class MatchThen<Token,  TLeft,  TRight,  TOut>  :  IRule<Token,  TOut>  {readonly  IRule<Token,  TLeft> leftRule;readonly  IRule<Token,  TRight> rightRule;

protected abstract TOut Combine(TLeft leftResult,  TRight rightResult);

public MatchThen(IRule<Token,  TLeft> leftRule,  IRule<Token,  TRight> rightRule)  {this.leftRule  = leftRule;this.rightRule  = rightRule;

}

public bool  TryParse(ISource<Token> source,  out TOut result)  {int originalIndex  = source.CurrentIndex;result  = default(TOut);TLeft leftResult;if (!leftRule.TryParse(source,  out  leftResult))  {

source.Move(originalIndex);return false;

}

TRight rightResult;if (!rightRule.TryParse(source,  out  rightResult))  {

source.Move(originalIndex);return false;

}

result  = Combine(leftResult,  rightResult);return true;

}}

Page 30: Functional concepts in C#

Match Then...

public class MatchThenKeep<Token,  TLeft,  TRight>  :  MatchThen<Token,  TLeft,  TRight,  TRight>  {public MatchThenKeep(IRule<Token,  TLeft> leftRule,  IRule<Token,  TRight> rightRule)  :  base(leftRule,  rightRule)  {  }

protected override  TRight Combine(TLeft leftResult,  TRight rightResult)  =>  rightResult;}

public class MatchThenIgnore<Token,  TLeft,  TRight>  :  MatchThen<Token,  TLeft,  TRight,  TLeft>  {public MatchThenIgnore(IRule<Token,  TLeft> leftRule,  IRule<Token,  TRight> rightRule)  :  base(leftRule,  rightRule)  {  }

protected override  TLeft Combine(TLeft leftResult,  TRight rightResult)  =>  leftResult;}

Page 31: Functional concepts in C#

Invert Rule (Not)public class Not<Token,  TResult>  :  IRule<Token,  Token>  {

readonly  IRule<Token,  TResult> rule;public Not(IRule<Token,  TResult> rule)  {  this.rule  = rule;  }

public bool  TryParse(ISource<Token> source,  out Token result)  {result  = default(Token);if (!source.HasMore)

return false;

int originalIndex  = source.CurrentIndex;TResult throwAwayResult;bool  matches  = rule.TryParse(source,  out  throwAwayResult);if (matches){

source.Move(originalIndex);return false;

}

source.Move(originalIndex);result  = source.Current;source.Move(originalIndex  + 1);return true;

}}

Spot the bug!

Page 32: Functional concepts in C#

Many (Once, Zero, and more times)public class Many<Token,  TResult>  :  IRule<Token,  TResult[]>  {

readonly  IRule<Token,  TResult> rule;readonly  bool  requireAtLeastOne;

public Many(IRule<Token,  TResult> rule,  bool requireAtLeastOne)  {  this.rule  = rule;  this.requireAtLeastOne  = requireAtLeastOne;  }

public bool  TryParse(ISource<Token> source,  out TResult[]  results)  {List<TResult> buffer  = new List<TResult>();while (source.HasMore)  {

int originalIndex  = source.CurrentIndex;TResult result;bool  matched  = rule.TryParse(source,  out  result);if (!matched)  {

source.Move(originalIndex);break;

}

buffer.Add(result);}

if (requireAtLeastOne  && buffer.Count == 0)  {results  = null;return false;

}

results  = buffer.ToArray();return true;

}}

Page 33: Functional concepts in C#

Map Resultpublic abstract class MapTo<Token,  TIn,  TOut>  :  IRule<Token,  TOut>  {

readonly  IRule<Token,  TIn> rule;protected MapTo(IRule<Token,  TIn> rule)  {  this.rule  = rule;  }

protected abstract TOut Convert(TIn value);

public bool  TryParse(ISource<Token> source,  out TOut result)  {result  = default(TOut);

int originalIndex  = source.CurrentIndex;TIn resultIn;if (!rule.TryParse(source,  out  resultIn))  {

source.Move(originalIndex);return false;

}

result  = Convert(resultIn);return true;

}}

Page 34: Functional concepts in C#

Map Resultpublic class JoinText :  MapTo<char,  char[],  string>  {

public JoinText(IRule<char,  char[]> rule)  :  base(rule)  {  }

protected override  string  Convert(char[]  value)  =>  new  string(value);

}

public class MapToInteger :  MapTo<char,  string,  int>  {

public MapToInteger(IRule<char,  string> rule)  :  base(rule)  {  }

protected override  int Convert(string value)  =>  int.Parse(value);

}

Page 35: Functional concepts in C#

Putting the blocks together

var  quote  = new CharIs('"');var  slash  = new CharIs('\\');var  escapedQuote  = new MatchThenKeep<char,  char,  char>(slash,  quote);var  escapedSlash  = new MatchThenKeep<char,  char,  char>(slash,  slash);var  notQuote  = new Not<char,  char>(quote);

var  insideQuoteChar  = new FirstMatch<char,  char>(new[]  {(IRule<char,  char>)escapedQuote,escapedSlash,notQuote

});

var  insideQuote  = new Many<char,  char>(insideQuoteChar,  false);

var  insideQuoteAsString  = new JoinText(insideQuote);var  openQuote  = new MatchThenKeep<char,  char,  string>(quote,  insideQuoteAsString);var  fullQuote  = new MatchThenIgnore<char,  string,  char>(openQuote,  quote);

var  source  = new StringSource(raw);

string  asQuote;if (fullQuote.TryParse(source,  out  asQuote))

return asQuote;

source.Move(0);int asInteger;if (digitsAsInt.TryParse(source,  out  asInteger))

return asInteger;

return null;

var  digit  = new CharIsDigit();var  digits  = new Many<char,  char>(digit,  true);var  digitsString  = new JoinText(digits);var  digitsAsInt  = new MapToInteger(digitsString);

Page 36: Functional concepts in C#

A Comparison

Page 37: Functional concepts in C#

A Comparison

Page 38: Functional concepts in C#

A Comparison

Page 39: Functional concepts in C#

What an Improvement!

Page 40: Functional concepts in C#

A Comparison (just the definition)

Page 41: Functional concepts in C#

Room for Improvementpublic abstract class MatchThen<Token,  TLeft,  TRight,  TOut>  :  IRule<Token,  TOut>  {

readonly  IRule<Token,  TLeft> leftRule;readonly  IRule<Token,  TRight> rightRule;

protected abstract TOut Combine(TLeft leftResult,  TRight rightResult);

public MatchThen(IRule<Token,  TLeft> leftRule,  IRule<Token,  TRight> rightRule)  {this.leftRule  = leftRule;this.rightRule  = rightRule;

}

public bool  TryParse(ISource<Token> source,  out TOut result)  {int originalIndex  = source.CurrentIndex;result  = default(TOut);TLeft leftResult;if (!leftRule.TryParse(source,  out  leftResult))  {

source.Move(originalIndex);return false;

}

TRight rightResult;if (!rightRule.TryParse(source,  out  rightResult))  {

source.Move(originalIndex);return false;

}

result  = Combine(leftResult,  rightResult);return true;

}}

Out parameter :(

Managing the source’s index

Page 42: Functional concepts in C#

Iteration 2.1

Immutability

Page 43: Functional concepts in C#

An Immutable Sourcepublic interface ISource<Token>  {

Token Current {  get;  }

bool  HasMore {  get;  }

int CurrentIndex {  get;  }

void Move(int index);

}

public interface ISource<Token>  {

Token Current {  get;  }

bool  HasMore {  get;  }

ISource<Token> Next();

}

Page 44: Functional concepts in C#

An Immutable Source

public class StringSource :  ISource<char>  {

readonly  string  value;

int index;

public StringSource(string value)  {  

this.value  = value;  }

public char Current  => value[index];

public int CurrentIndex  => index;

public bool  HasMore  => index  < value.Length;

public void Move(int index)  =>  this.index  =  index;

}

public class StringSource :  ISource<char>  {

readonly  string  value;

readonly  int index;

public StringSource(string value,  int index =  0)  {  

this.value  = value;  this.index  = index;  }

public char Current  => value[index];

public bool  HasMore  => index  < value.Length;

public ISource<char> Next()  =>  

new  StringSource(value,  index +  1);

}

Page 45: Functional concepts in C#

Ditch the Outpublic class Result<Token,  TValue>  {

public bool  Success {  get;  }public TValue Value {  get;  }public string  Message {  get;  }public ISource<Token> Next {  get;  }

public Result(bool success,  TValue value,  string message,  ISource<Token> next)  {Success = success;Value = value;Message = message;Next = next;

}}

public interface IRule<Token,  TValue>  {

Result<Token,  TValue> TryParse(ISource<Token> source);

}

Page 46: Functional concepts in C#

Char Matches...public abstract class CharMatches :  IRule<char,  char>  {

protected abstract bool  IsCharMatch(char c);

public bool  TryParse(ISource<char> source,  out char result)  {result  = default(char);if (!source.HasMore)

return false;if (!IsCharMatch(source.Current))

return false;result  = source.Current;source.Move(source.CurrentIndex + 1);return true;

}}

public abstract class CharMatches :  IRule<char,  char>  {

protected abstract bool  IsCharMatch(char c);

public Result<char,  char> TryParse(ISource<char> source)  {

if (!source.HasMore)

return new Result<char,  char>(false,  '\0',  "Unexpected  EOF",  null);

if (!IsCharMatch(source.Current))

return new Result<char,  char>(false,  '\0',  $"Unexpected  char:  {source.Current}",  null);

return new Result<char,  char>(true,  source.Current,  null,  source.Next());

}

}

Page 47: Functional concepts in C#

These Don’t Change

public class CharIsDigit :  CharMatches  {protected override  bool  IsCharMatch(char c)  =>  char.IsDigit(c);

}

public class CharIs :  CharMatches  {

readonly  char toMatch;

public CharIs(char toMatch)  {  this.toMatch  = toMatch;  }

protected override  bool  IsCharMatch(char c)  =>  c  ==  toMatch;

}

Page 48: Functional concepts in C#

First Match public class FirstMatch<Token,  TResult>  :  IRule<Token,  TResult>  {

readonly  IRule<Token,  TResult>[]  rules;public FirstMatch(IRule<Token,  TResult>[]  rules)  {  this.rules  = rules;  }

public bool  TryParse(ISource<Token> source,  out TResult result)  {foreach(var  rule  in  rules)  {

int originalIndex  = source.CurrentIndex;if (rule.TryParse(source,  out  result))

return true;source.Move(originalIndex);

}

result  = default(TResult);return false;

}}

public class FirstMatch<Token,  TResult>  :  IRule<Token,  TResult>  {readonly  IRule<Token,  TResult>[]  rules;public FirstMatch(IRule<Token,  TResult>[]  rules)  {  this.rules  = rules;  }

public Result<Token,  TResult> TryParse(ISource<Token> source)  {foreach  (var  rule  in  rules)  {

var  result  = rule.TryParse(source);if (result.Success)

return result;}

return new Result<Token,  TResult>(false,  default(TResult),  "No  rule  matched",  null);}

}

Page 49: Functional concepts in C#

Match Then...public bool  TryParse(ISource<Token> source,  out TOut result)  {

int originalIndex  = source.CurrentIndex;result  = default(TOut);TLeft leftResult;if (!leftRule.TryParse(source,  out  leftResult))  {

source.Move(originalIndex);return false;

}

TRight rightResult;if (!rightRule.TryParse(source,  out  rightResult))  {

source.Move(originalIndex);return false;

}

result  = Combine(leftResult,  rightResult);return true;

}

public Result<Token,  TOut> TryParse(ISource<Token> source)  {var  leftResult  = leftRule.TryParse(source);if (!leftResult.Success)

return new Result<Token,  TOut>(false,  default(TOut),  leftResult.Message,  null);

var  rightResult  = rightRule.TryParse(leftResult.Next);if (!rightResult.Success)

return new Result<Token,  TOut>(false,  default(TOut),  rightResult.Message,  null);

var  result  = Combine(leftResult.Value,  rightResult.Value);return new Result<Token,  TOut>(true,  result,  null,  rightResult.Next);

}

Page 50: Functional concepts in C#

Invert Rule (Not)public class Not<Token,  TResult>  :  IRule<Token,  Token>  {

readonly  IRule<Token,  TResult> rule;public Not(IRule<Token,  TResult> rule)  {  this.rule  = rule;  }

public bool  TryParse(ISource<Token> source,  out Token result)  {result  = default(Token);if (!source.HasMore)

return false;

int originalIndex  = source.CurrentIndex;TResult throwAwayResult;bool  matches  = rule.TryParse(source,  out  throwAwayResult);if (matches){

source.Move(originalIndex);return false;

}

source.Move(originalIndex);result  = source.Current;source.Move(originalIndex  + 1);return true;

}}

public class Not<Token,  TResult>  :  IRule<Token,  Token>  {readonly  IRule<Token,  TResult> rule;public Not(IRule<Token,  TResult> rule)  {  this.rule  = rule;  }

public Result<Token,  Token> TryParse(ISource<Token> source)  {if (!source.HasMore)

return new Result<Token,  Token>(false,  default(Token),  "Unexpected  EOF",  null);

var  result  = rule.TryParse(source);if (result.Success)

return new Result<Token,  Token>(false,  default(Token),  "Unexpected  match",  null);

return new Result<Token,  Token>(true,  source.Current,  null,  source.Next());

}}

Page 51: Functional concepts in C#

Getting Better, but...

Page 52: Functional concepts in C#

Still Room for Improvement

public class Result<Token,  TValue>  {public bool  Success {  get;  }public TValue Value {  get;  }public string  Message {  get;  }public ISource<Token> Next {  get;  }

public Result(bool success,  TValue value,  string message,  ISource<Token> next)  {Success = success;Value = value;Message = message;Next = next;

}}

Only valid when Success = true

Only valid when Success = false

Page 53: Functional concepts in C#

Iteration 2.2

Discriminated Unions and Pattern Matching (sorta)

Page 54: Functional concepts in C#

Two States (Simple “Result” Example)

public interface IResult {  }

public class SuccessResult<TValue>  :  IResult  {

public TValue Value {  get;  }

public SuccessResult(TValue value)  {  Value = value;  }

}

public class ErrorResult :  IResult  {

public string  Message {  get;  }

public ErrorResult(string message)  {  Message = message;  }

}

Page 55: Functional concepts in C#

Two States (The Matching)

IResult result  = ParseIt();

if (result  is  SuccessResult<string>)  {

var  success  = (SuccessResult<string>)result;

Console.WriteLine($"SUCCESS:  {success.Value}");

}  else if (result  is  ErrorResult)  {

var  error  = (ErrorResult)result;

Console.WriteLine($"ERR:  {error.Message}");

}

Page 56: Functional concepts in C#

Pattern Matching(ish)public interface IResult<TValue>  {

T  Match<T>(Func<SuccessResult<TValue>,  T> success,Func<ErrorResult<TValue>,  T> error);

}

public class SuccessResult<TValue>  :  IResult<TValue>  {public TValue Value {  get;  }public SuccessResult(TValue value)  {  Value = value;  }public T  Match<T>(Func<SuccessResult<TValue>,  T> success,

Func<ErrorResult<TValue>,  T> error)  =>  success(this);}

public class ErrorResult<TValue>  :  IResult<TValue>{

public string  Message {  get;  }public ErrorResult(string message)  {  Message = message;  }public T  Match<T>(Func<SuccessResult<TValue>,  T> success,

Func<ErrorResult<TValue>,  T> error)  =>  error(this);}

Page 57: Functional concepts in C#

Pattern Matching(ish)

IResult<string> result  = ParseIt();

string  message  = result.Match(

success  => $"SUCCESS:  ${success.Value}",

error  => $"ERR:  {error.Message}");

Console.WriteLine(message);

IResult result  = ParseIt();

if (result  is  SuccessResult<string>)  {

var  success  = (SuccessResult<string>)result;

Console.WriteLine($"SUCCESS:  {success.Value}");

}  else if (result  is  ErrorResult)  {

var  error  = (ErrorResult)result;

Console.WriteLine($"ERR:  {error.Message}");

}

Page 58: Functional concepts in C#

The Match Method Forces us to handle all cases

Gives us an object with only valid properties for that state

Page 59: Functional concepts in C#

The New IResultpublic interface IResult<Token,  TValue>  {

T  Match<T>(Func<FailResult<Token,  TValue>,  T> fail,Func<SuccessResult<Token,  TValue>,  T> success);

}

public class FailResult<Token,  TValue>  :  IResult<Token,  TValue>  {public string  Message {  get;  }public FailResult(string message)  {  Message = message;  }public T  Match<T>(Func<FailResult<Token,  TValue>,  T> fail,

Func<SuccessResult<Token,  TValue>,  T> success)  =>  fail(this);}

public class SuccessResult<Token,  TValue>  :  IResult<Token,  TValue>  {public TValue Value {  get;  }public ISource<Token> Next {  get;  }

public SuccessResult(TValue value,  ISource<Token> next)  {  Value = value;  Next = next;  }

public T  Match<T>(Func<FailResult<Token,  TValue>,  T> fail,Func<SuccessResult<Token,  TValue>,  T> success)  =>  success(this);

}

Page 60: Functional concepts in C#

ISource also Represents Two States

public interface ISource<Token>  {

Token Current {  get;  }

bool  HasMore {  get;  }

ISource<Token> Next();

}

Only valid when HasMore = true

Page 61: Functional concepts in C#

The New ISourcepublic interface ISource<Token>  {

T  Match<T>(Func<EmtySource<Token>,  T> empty,Func<SourceWithMoreContent<Token>,  T> hasMore);

}

public class EmtySource<Token>  :  ISource<Token>  {//  No  properties!    No  state!    Let's  just  make  it  singleton.EmtySource()  {  }

public static readonly  EmtySource<Token> Instance  = new EmtySource<Token>();

public T  Match<T>(Func<EmtySource<Token>,  T> empty,Func<SourceWithMoreContent<Token>,  T> hasMore)  =>  empty(this);

}

public class SourceWithMoreContent<Token>  :  ISource<Token>  {readonly  Func<ISource<Token>> getNext;

public SourceWithMoreContent(Token current,  Func<ISource<Token>> getNext)  {  Current = current;  this.getNext  = getNext;  }

public Token Current {  get;  set;  }public ISource<Token> Next()  =>  getNext();

public T  Match<T>(Func<EmtySource<Token>,  T> empty,Func<SourceWithMoreContent<Token>,  T> hasMore)  =>  hasMore(this);

}

Page 62: Functional concepts in C#

Make a String Source

public static class StringSource {public static ISource<char> Create(string value,  int index =  0)  {

if (index  >= value.Length)return EmtySource<char>.Instance;

return new SourceWithMoreContent<char>(value[index],  ()  => Create(value,  index  + 1));}

}

public static ISource<char> Create(string  value,  int index  = 0)=> index  >= value.Length

? (ISource<char>)EmtySource<char>.Instance: new SourceWithMoreContent<char>(value[index],  ()  => Create(value,  index  + 1));

Page 63: Functional concepts in C#

Char Matches... public abstract class CharMatches :  IRule<char,  char>  {

protected abstract bool  IsCharMatch(char c);

public Result<char,  char> TryParse(ISource<char> source)  {

if (!source.HasMore)

return new Result<char,  char>(false,  '\0',  "Unexpected  EOF",  null);

if (!IsCharMatch(source.Current))

return new Result<char,  char>(false,  '\0',  $"Unexpected  char:  {source.Current}",  null);

return new Result<char,  char>(true,  source.Current,  null,  source.Next());

}

}

public abstract class CharMatches :  IRule<char,  char>  {protected abstract bool  IsCharMatch(char c);

public IResult<char,  char> TryParse(ISource<char> source)  {var  result  = source.Match(

empty  => (IResult<char,  char>)new FailResult<char,  char>("Unexpected  EOF"),hasMore  =>{

if (!IsCharMatch(hasMore.Current))return new FailResult<char,  char>($"Unexpected  char:  {hasMore.Current}");

return new SuccessResult<char,  char>(hasMore.Current,  hasMore.Next());});

return result;}

}

public IResult<char,  char> TryParse(ISource<char> source)

=> source.Match(

empty  => new FailResult<char,  char>("Unexpected  EOF"),

hasMore  => IsCharMatch(hasMore.Current)

? new SuccessResult<char,  char>(hasMore.Current,  hasMore.Next())

: (IResult<char,  char>)new FailResult<char,  char>($"Unexpected  char:  {hasMore.Current}")

);

Page 64: Functional concepts in C#

Match Then...

public IResult<Token,  TOut> TryParse(ISource<Token> source)  {var  leftResult  = leftRule.TryParse(source);var  finalResult  = leftResult.Match(

leftFail  => new FailResult<Token,  TOut>(leftFail.Message),leftSuccess  => {

var  rightResult  = rightRule.TryParse(leftSuccess.Next);var  rightFinalResult  = rightResult.Match(

rightFail  => (IResult<Token,  TOut>)new FailResult<Token,  TOut>(rightFail.Message),rightSuccess  => {

var  finalValue  = Combine(leftSuccess.Value,  rightSuccess.Value);return new SuccessResult<Token,  TOut>(finalValue,  rightSuccess.Next);

});return rightFinalResult;

});

return finalResult;}

public Result<Token,  TOut> TryParse(ISource<Token> source)  {var  leftResult  = leftRule.TryParse(source);if (!leftResult.Success)

return new Result<Token,  TOut>(false,  default(TOut),  leftResult.Message,  null);

var  rightResult  = rightRule.TryParse(leftResult.Next);if (!rightResult.Success)

return new Result<Token,  TOut>(false,  default(TOut),  rightResult.Message,  null);

var  result  = Combine(leftResult.Value,  rightResult.Value);return new Result<Token,  TOut>(true,  result,  null,  rightResult.Next);

}

public IResult<Token,  TOut> TryParse(ISource<Token> source)=> leftRule.TryParse(source).Match(

leftFail  => new FailResult<Token,  TOut>(leftFail.Message),leftSuccess  =>

rightRule.TryParse(leftSuccess.Next).Match(rightFail  => (IResult<Token,  TOut>)new FailResult<Token,  TOut>(rightFail.Message),rightSuccess  => new SuccessResult<Token,  TOut>(Combine(leftSuccess.Value,  rightSuccess.Value),  

rightSuccess.Next))

);

Page 65: Functional concepts in C#

Invert Rule (Not)public Result<Token,  Token> TryParse(ISource<Token> source)  {

if (!source.HasMore)return new Result<Token,  Token>(false,  default(Token),  "Unexpected  EOF",  null);

var  result  = rule.TryParse(source);if (result.Success)

return new Result<Token,  Token>(false,  default(Token),  "Unexpected  match",  null);

return new Result<Token,  Token>(true,  source.Current,  null,  source.Next());}

public IResult<Token,  Token> TryParse(ISource<Token> source)

=> source.Match(

empty  => new FailResult<Token,  Token>("Unexpected  EOF"),

current  => rule.TryParse(current).Match(

fail  => new SuccessResult<Token,  Token>(current.Current,  current.Next()),

success  => (IResult<Token,  Token>)new FailResult<Token,  Token>("Unexpected  match")

)

);

Page 66: Functional concepts in C#

That’s nice but...

Page 67: Functional concepts in C#

Let’s Be HonestAll these `new` objects are ugly.

var  quote  = new CharIs('"');var  slash  = new CharIs('\\');var  escapedQuote  = new MatchThenKeep<char,  char,  char>(slash,  quote);var  escapedSlash  = new MatchThenKeep<char,  char,  char>(slash,  slash);var  notQuote  = new Not<char,  char>(quote);

var  insideQuoteChar  = new FirstMatch<char,  char>(new[]  {(IRule<char,  char>)escapedQuote,escapedSlash,notQuote

});

var  insideQuote  = new Many<char,  char>(insideQuoteChar,  false);

var  insideQuoteAsString  = new JoinText(insideQuote);var  openQuote  = new MatchThenKeep<char,  char,  string>(quote,  insideQuoteAsString);var  fullQuote  = new MatchThenIgnore<char,  string,  char>(openQuote,  quote);

Page 68: Functional concepts in C#

AlsoSingle method interfaces are lame*.

It’s effectively a delegate.

public interface IRule<Token,  TValue>  {

IResult<Token,  TValue> TryParse(ISource<Token> source);

}

*In  a  non-­scientific  poll  of  people  who  agree  with  me,  100%  of  respondents  confirmed  this  statement.    Do  not  question  its  validity.

Page 69: Functional concepts in C#

Iteration 3.0

Functions as First Class Citizens

Page 70: Functional concepts in C#

A Rule is a Delegate is a Function

public interface IRule<Token,  TValue>  {

IResult<Token,  TValue> TryParse(ISource<Token> source);

}

public delegate  IResult<Token,  TValue> Rule<Token,  TValue>(ISource<Token> source);

Page 71: Functional concepts in C#

Char Matches...public abstract class CharMatches :  IRule<char,  char>  {

protected abstract bool  IsCharMatch(char c);

public IResult<char,  char> TryParse(ISource<char> source)=>  source.Match(

empty =>  new FailResult<char,  char>("Unexpected EOF"),hasMore  =>  IsCharMatch(hasMore.Current)

?  new  SuccessResult<char,  char>(hasMore.Current,  hasMore.Next()):  (IResult<char,  char>)new  FailResult<char,  char>($"Unexpected  char:  {hasMore.Current}")

);}

public static class Rules {public static Rule<char,  char> CharMatches(Func<char,  bool> isMatch)

=>  (source)  =>  source.Match(empty =>  new FailResult<char,  char>("Unexpected EOF"),hasMore  =>  isMatch(hasMore.Current)

?  new  SuccessResult<char,  char>(hasMore.Current,  hasMore.Next()):  (IResult<char,  char>)new  FailResult<char,  char>($"Unexpected  char:  {hasMore.Current}")

);}

public static Rule<char,  char> CharIsDigit()  => CharMatches(char.IsDigit);public static Rule<char,  char> CharIs(char c)  => CharMatches(x  => x  == c);

Page 72: Functional concepts in C#

Then (Keep|Ignore)public static Rule<Token,  TOut> MatchThen<Token,  TLeft,  TRight,  TOut>(this Rule<Token,  TLeft> leftRule,  Rule<Token,  TRight> rightRule,  Func<TLeft,  TRight,  TOut> convert)

=> (source)  => leftRule(source).Match(leftFail  => new FailResult<Token,  TOut>(leftFail.Message),leftSuccess  =>

rightRule(leftSuccess.Next).Match(rightFail  => (IResult<Token,  TOut>)new FailResult<Token,  TOut>(rightFail.Message),rightSuccess  => new SuccessResult<Token,  TOut>(convert(leftSuccess.Value,  rightSuccess.Value),  

rightSuccess.Next))

);

public static Rule<Token,  TRight> MatchThenKeep<Token,  TLeft,  TRight>(this Rule<Token,  TLeft> leftRule,  Rule<Token,  TRight> rightRule)

=> MatchThen(leftRule,  rightRule,  (left,  right)  => right);

public static Rule<Token,  TLeft> MatchThenIgnore<Token,  TLeft,  TRight>(this Rule<Token,  TLeft> leftRule,  Rule<Token,  TRight> rightRule)

=> MatchThen(leftRule,  rightRule,  (left,  right)  => left);

Page 73: Functional concepts in C#

Not, MapTo, JoinText, MapToIntegerpublic static Rule<Token,  Token> Not<Token,  TResult>(this Rule<Token,  TResult> rule)

=> (source)  => source.Match(empty  => new FailResult<Token,  Token>("Unexpected  EOF"),current  => rule(current).Match(

fail  => new SuccessResult<Token,  Token>(current.Current,  current.Next()),success  => (IResult<Token,  Token>)new FailResult<Token,  Token>("Unexpected  match")

));

public static Rule<Token,  TOut> MapTo<Token,  TIn,  TOut>(this Rule<Token,  TIn> rule,  Func<TIn,  TOut> convert)=> (source)  => rule(source).Match(

fail  => (IResult<Token,  TOut>)new FailResult<Token,  TOut>(fail.Message),success  => new SuccessResult<Token,  TOut>(convert(success.Value),  success.Next)

);

public static Rule<char,  string> JoinText(this Rule<char,  char[]> rule)=> MapTo(rule,  (x)  => new string(x));

public static Rule<char,  int> MapToInteger(this Rule<char,  string> rule)=> MapTo(rule,  (x)  => int.Parse(x));

Page 74: Functional concepts in C#

Example Usage

var  quote  = Rules.CharIs('"');

var  slash  = Rules.CharIs('\\');

var  escapedQuote  =  Rules.MatchThenKeep(slash,  quote);

var  escapedSlash  = slash.MatchThenKeep(slash);

Page 75: Functional concepts in C#

The Original 2.0 Definition

var  quote  = new CharIs('"');var  slash  = new CharIs('\\');var  escapedQuote  = new MatchThenKeep<char,  char,  char>(slash,  quote);var  escapedSlash  = new MatchThenKeep<char,  char,  char>(slash,  slash);var  notQuote  = new Not<char,  char>(quote);

var  insideQuoteChar  = new FirstMatch<char,  char>(new[]  {(IRule<char,  char>)escapedQuote,escapedSlash,notQuote

});

var  insideQuote  = new Many<char,  char>(insideQuoteChar,  false);

var  insideQuoteAsString  = new JoinText(insideQuote);var  openQuote  = new MatchThenKeep<char,  char,  string>(quote,  insideQuoteAsString);var  fullQuote  = new MatchThenIgnore<char,  string,  char>(openQuote,  quote);

var  source  = new StringSource(raw);

string  asQuote;if (fullQuote.TryParse(source,  out  asQuote))

return asQuote;

source.Move(0);int asInteger;if (digitsAsInt.TryParse(source,  out  asInteger))

return asInteger;

return null;

var  digit  = new CharIsDigit();var  digits  = new Many<char,  char>(digit,  true);var  digitsString  = new JoinText(digits);var  digitsAsInt  = new MapToInteger(digitsString);

Page 76: Functional concepts in C#

The Updated 3.0 Definition

var  quote  = Rules.CharIs('"');var  slash  = Rules.CharIs('\\');var  escapedQuote  = slash.MatchThenKeep(quote);var  escapedSlash  = slash.MatchThenKeep(slash);var  notQuote  = quote.Not();

var  fullQuote  = quote.MatchThenKeep(

Rules.FirstMatch(escapedQuote,escapedSlash,notQuote

).Many().JoinText()).MatchThenIgnore(quote);

var  finalResult  = Rules.FirstMatch(fullQuote.MapTo(x  => (object)x),digit.MapTo(x  => (object)x)

);

var  source  = StringSource.Create(raw);

return finalResult(source).Match(fail  => null,success  => success.Value

);

var  integer  = Rules.CharIsDigit().Many(true).JoinText().MapToInteger();

Page 77: Functional concepts in C#

A Comparison V1 -> V3

Page 78: Functional concepts in C#

A Comparison V1 -> V3

Page 79: Functional concepts in C#

A Comparison V1 -> V3 (Just Definition)

Page 80: Functional concepts in C#

Looks Great!My co-workers are going to kill me

Page 81: Functional concepts in C#

Is it a good idea?

public static Rule<Token,  Token> Not<Token,  TResult>(this Rule<Token,  TResult> rule)=> (source)  => source.Match(

empty  => new FailResult<Token,  Token>("Unexpected  EOF"),current  => rule(current).Match(

fail  => new SuccessResult<Token,  Token>(current.Current,  current.Next()),success  => (IResult<Token,  Token>)new FailResult<Token,  Token>("Unexpected  match")

));

public static Rule<Token,  TOut> MapTo<Token,  TIn,  TOut>(this Rule<Token,  TIn> rule,  Func<TIn,  TOut> convert)=> (source)  => rule(source).Match(

fail  => (IResult<Token,  TOut>)new FailResult<Token,  TOut>(fail.Message),success  => new SuccessResult<Token,  TOut>(convert(success.Value),  success.Next)

);

public static Rule<char,  string> JoinText(this Rule<char,  char[]> rule)=> MapTo(rule,  (x)  => new string(x));

public static Rule<char,  int> MapToInteger(this Rule<char,  string> rule)=> MapTo(rule,  (x)  => int.Parse(x));

Page 82: Functional concepts in C#

Limitations“At Zombocom, the only limit…

is yourself.”

1. Makes a LOT of short-lived objects (ISources, IResults).

2. As written currently, you will end up with the entire thing in memory.

3. Visual Studio’s Intellisense struggles with nested lambdas.

4. Frequently requires casts to solve type inference problems.

5. It’s not very C#.

Page 83: Functional concepts in C#

Let’s Review

Page 84: Functional concepts in C#

Iterations

Iteration 1.0: Procedural

Iteration 2.0: Making Compositional with OOP

Iteration 2.1: Immutability

Iteration 2.2: Discriminated Unions and Pattern Matching

Iteration 3.0: Functions as First Class Citizens

Page 85: Functional concepts in C#

That’s All

Thanks for coming and staying awake!