Last active
August 29, 2015 13:56
-
-
Save ToJans/9216351 to your computer and use it in GitHub Desktop.
Trying to convert a simple C# app to eloquent F#; failing so far; to verbose IMO
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using System; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.Linq; | |
namespace TDDCoverage | |
{ | |
public class Order | |
{ | |
public class Line | |
{ | |
public readonly String ArticleId; | |
public readonly decimal Amount; | |
public Line(String ArticleId, decimal Amount) | |
{ | |
this.ArticleId = ArticleId; | |
this.Amount = Amount; | |
} | |
} | |
public readonly IEnumerable<Line> Lines; | |
public Order(IEnumerable<Line> Lines) | |
{ | |
this.Lines = Lines; | |
} | |
public class LineList : List<Line> | |
{ | |
public void Add(String ArticleId, decimal Amount) | |
{ | |
this.Add(new Line(ArticleId,Amount)); | |
} | |
} | |
} | |
public class Invoice | |
{ | |
public class Item | |
{ | |
public readonly String Name; | |
public readonly decimal Amount, Price, VAT; | |
public Item(String Name, decimal Amount, decimal Price, decimal VAT) | |
{ | |
this.Name = Name; | |
this.Amount = Amount; | |
this.Price = Price; | |
this.VAT = VAT; | |
} | |
public override bool Equals(object obj) | |
{ | |
var x = obj as Item; | |
return x != null && | |
x.Name == Name && | |
x.Price == Price && | |
x.VAT == VAT && | |
x.Amount == Amount; | |
} | |
public override int GetHashCode() | |
{ | |
return Name.GetHashCode() ^ Price.GetHashCode() ^ VAT.GetHashCode() ^ Amount.GetHashCode(); | |
} | |
} | |
public readonly IEnumerable<Item> Items; | |
public Invoice(IEnumerable<Item> Items) | |
{ | |
this.Items = Items; | |
} | |
public class ItemList:List<Item> | |
{ | |
public void Add(String Name, decimal Amount, decimal Price, decimal VAT) | |
{ | |
this.Add(new Item(Name,Amount,Price,VAT)); | |
} | |
} | |
} | |
public enum VATCode | |
{ | |
Unknown, | |
Regular, | |
Food, | |
None | |
} | |
public class VATRates : Dictionary<VATCode, decimal> { } | |
public class Article | |
{ | |
public readonly string Id; | |
public readonly VATCode VATCode; | |
public readonly string Name; | |
public readonly decimal Price; | |
public Article(string Id, string Name, decimal Price, VATCode VAT) | |
{ | |
this.Id = Id; | |
this.Name = Name; | |
this.Price = Price; | |
this.VATCode = VAT; | |
} | |
public class Dictionary : Dictionary<string, Article> | |
{ | |
public void Add(string Id, string Name, int Price, VATCode VATCode) | |
{ | |
this.Add(Id, new Article(Id, Name, Price, VATCode)); | |
} | |
} | |
} | |
public class ArticleNotFoundException : Exception { } | |
public class VATRateNotFoundException : Exception { } | |
class Invoicer | |
{ | |
private VATRates VATRates; | |
private Article.Dictionary Articles; | |
public Invoicer(VATRates VATRates, Article.Dictionary Articles) | |
{ | |
this.VATRates = VATRates; | |
this.Articles = Articles; | |
} | |
public Invoice BillOrder(Order o) | |
{ | |
Invoice.ItemList items = new Invoice.ItemList(); | |
foreach (var orderline in o.Lines) | |
{ | |
Article article; | |
Decimal vatcode; | |
if (!Articles.TryGetValue(orderline.ArticleId, out article)) throw new ArticleNotFoundException(); | |
if (!VATRates.TryGetValue(article.VATCode, out vatcode)) throw new VATRateNotFoundException(); | |
var total = orderline.Amount * article.Price; | |
items.Add(article.Name, orderline.Amount, total, total * vatcode); | |
} | |
return new Invoice(items); | |
} | |
} | |
[TestClass] | |
public class Bill_order_tests | |
{ | |
Invoicer SUT; | |
Order.LineList OrderLines; | |
string unknown_article_id, article_id_with_unknown_vat; | |
[TestInitialize] | |
public void Setup() | |
{ | |
var rates = new VATRates(); | |
rates.Add(VATCode.Regular, .21M); | |
rates.Add(VATCode.Food, .06M); | |
rates.Add(VATCode.None, 0); | |
var articles = new Article.Dictionary(); | |
articles.Add("HTC-123", "HTC One Two TTT", 1000, VATCode.Regular); | |
articles.Add("APPL", "Apple blah", 1, VATCode.Food); | |
articles.Add("RNV", "Stairs renovation", 1500, VATCode.None); | |
articles.Add("???", "VAT Unknown", 123, VATCode.Unknown); | |
SUT = new Invoicer(rates, articles); | |
OrderLines = new Order.LineList(); | |
OrderLines.Add("HTC-123", 1); | |
OrderLines.Add("APPL", 12); | |
OrderLines.Add("RNV", 1); | |
unknown_article_id = "XYZ"; | |
article_id_with_unknown_vat = "???"; | |
} | |
[TestMethod] | |
public void Bill_an_order() | |
{ | |
var invoice = SUT.BillOrder(new Order(OrderLines)); | |
var invoiceItems = invoice.Items.ToArray(); | |
var expectedItems = new Invoice.ItemList(); | |
expectedItems.Add("HTC One Two TTT", 1, 1000, 210); | |
expectedItems.Add("Apple blah", 12, 12, 0.72M); | |
expectedItems.Add("Stairs renovation", 1, 1500, 0); | |
Assert.AreEqual(invoiceItems[0], expectedItems[0]); | |
Assert.AreEqual(invoiceItems[1], expectedItems[1]); | |
Assert.AreEqual(invoiceItems[2], expectedItems[2]); | |
} | |
[TestMethod] | |
[ExpectedException(typeof(ArticleNotFoundException))] | |
public void Bill_an_order_with_an_unkown_article() | |
{ | |
OrderLines.Add(unknown_article_id, 5); | |
SUT.BillOrder(new Order(OrderLines)); | |
} | |
[TestMethod] | |
[ExpectedException(typeof(VATRateNotFoundException))] | |
public void Bill_an_order_with_an_unknown_VAT_code() | |
{ | |
OrderLines.Add(article_id_with_unknown_vat, 5); | |
SUT.BillOrder(new Order(OrderLines)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace UnitTestProject1 | |
open System | |
open Microsoft.VisualStudio.TestTools.UnitTesting | |
type OrderLine = { ArticleId: string; Amount: decimal } | |
type OrderLines = OrderLine list | |
type Order = { Lines: OrderLines} | |
type VATType = | Unknown | Regular | Food | NoVat | |
type VatRates = Map<VATType, decimal> | |
type InvoiceItem = {Name:string; Amount: decimal; Price: decimal; VAT: decimal} | |
type Invoice = { Items: InvoiceItem list} | |
type Article = {Name: string; Price: decimal; VATCode: VATType} | |
type Articles = Map<string,Article> | |
exception ArticleNotFoundException | |
exception VATRateNotFoundException | |
type Result<'T> = Success of 'T | Failure of exn | |
module Invoicer = | |
let BillOrder (order: Order) (vatrates: VatRates) (articles: Articles) = | |
let get_art_vat (art:Article) = vatrates |> Map.tryFind art.VATCode | |
let build_line (ol:OrderLine, art: Article, vat:decimal) = | |
{ | |
Name = art.Name | |
Amount = ol.Amount | |
Price = art.Price*ol.Amount | |
VAT = vat*art.Price*ol.Amount | |
} | |
let process_line (line: OrderLine) = | |
let art = articles |> Map.tryFind line.ArticleId | |
let vat = art |> Option.bind get_art_vat | |
match art, vat with | |
| None, _ -> Failure ArticleNotFoundException | |
| _, None -> Failure VATRateNotFoundException | |
| Some(a), Some(v) -> Success (build_line(line, a, v)) | |
let rec process_all lines acc = | |
match lines with | |
| [] -> Success (acc |> List.rev) | |
| x::xs -> | |
match process_line(x) with | |
| Failure ex -> Failure ex | |
| Success line -> process_all xs (line::acc) | |
process_all order.Lines [] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment