Skip to content

Instantly share code, notes, and snippets.

@ToJans
Last active August 29, 2015 13:56
Show Gist options
  • Save ToJans/9216351 to your computer and use it in GitHub Desktop.
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
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));
}
}
}
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