ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

F# Functional Domain Models: Order Processing

F#

Goal -- WPM

Ready
Exercise Algorithm Area
1(*@
2Represents a unique identifier for a customer.
3*@)
4type CustomerId = CustomerId of int
5
6(*@
7Represents a unique identifier for a product.
8*@)
9type ProductId = ProductId of int
10
11(*@
12Represents a product with its ID, name, and price.
13*@)
14type Product = {
15Id: ProductId;
16Name: string;
17Price: decimal;
18}
19
20(*@
21Represents an item within an order.
22Includes the product, quantity, and calculated subtotal.
23*@)
24type OrderItem = {
25Product: Product;
26Quantity: int;
27Subtotal: decimal;
28}
29
30(*@
31Represents a customer with their ID and name.
32*@)
33type Customer = {
34Id: CustomerId;
35Name: string;
36}
37
38(*@
39Represents an order.
40Includes customer, items, status, and total price.
41This is an immutable record.
42*@)
43type Order = {
44Id: int;
45Customer: Customer;
46Items: list<OrderItem>;
47Status: OrderStatus;
48TotalPrice: decimal;
49}
50
51(*@
52Defines the possible statuses of an order.
53*@)
54type OrderStatus =
55| Pending
56| Processing
57| Shipped
58| Cancelled
59
60(*@
61Creates a new, empty order for a given customer.
62*@)
63let createOrder (customerId: int) (customerName: string) : Order =
64{
65Id = System.Random().Next(1000, 9999); // Assign a random ID
66Customer = { Id = CustomerId customerId; Name = customerName };
67Items = [];
68Status = Pending;
69TotalPrice = 0.0m;
70}
71
72(*@
73Adds an item to an existing order.
74Returns a new Order record with the item added and total price updated.
75Handles cases where the order is not in a state to accept items.
76*@)
77let addItemToOrder (order: Order) (product: Product) (quantity: int) : Result<Order, string> =
78if order.Status <> Pending then
79Error "Cannot add items to an order that is not pending."
80elif quantity <= 0 then
81Error "Quantity must be positive."
82else
83let subtotal = product.Price * decimal quantity
84let newItem = {
85Product = product;
86Quantity = quantity;
87Subtotal = subtotal;
88}
89let newItems = order.Items @ [newItem]
90let newTotalPrice = order.TotalPrice + subtotal
91Ok {
92order with
93Items = newItems;
94TotalPrice = newTotalPrice;
95}
96
97(*@
98Calculates the total price of an order based on its items.
99This is a helper function, the main `Order` record keeps `TotalPrice` updated.
100*@)
101let calculateOrderTotal (order: Order) : decimal =
102order.Items |> List.sumBy (fun item -> item.Subtotal)
103
104(*@
105Processes an order, changing its status to Processing.
106Returns a new Order record.
107*@)
108let processOrder (order: Order) : Result<Order, string> =
109if order.Status <> Pending then
110Error "Order must be pending to be processed."
111else
112Ok {
113order with
114Status = Processing;
115}
116
117(*@
118Ships an order, changing its status to Shipped.
119Returns a new Order record.
120*@)
121let shipOrder (order: Order) : Result<Order, string> =
122match order.Status with
123| Pending | Processing ->
124Ok {
125order with
126Status = Shipped;
127}
128| Shipped | Cancelled ->
129Error "Order cannot be shipped if already shipped or cancelled."
130
131(*@
132Cancels an order, changing its status to Cancelled.
133Returns a new Order record.
134*@)
135let cancelOrder (order: Order) : Result<Order, string> =
136match order.Status with
137| Pending | Processing ->
138Ok {
139order with
140Status = Cancelled;
141}
142| Shipped | Cancelled ->
143Error "Order cannot be cancelled if already shipped or cancelled."
144
145(*@
146A helper to create a sample product.
147*@)
148let createSampleProduct (id: int) (name: string) (price: decimal) : Product =
149{
150Id = ProductId id;
151Name = name;
152Price = price;
153}
154
155(*@
156A helper to create a sample customer.
157*@)
158let createSampleCustomer (id: int) (name: string) : Customer =
159{
160Id = CustomerId id;
161Name = name;
162}
163
164(*@
165A more complex order creation scenario.
166*@)
167let createComplexOrder () : Order =
168let customer = createSampleCustomer 1 "Alice"
169let order = createOrder customer.Id customer.Name
170let product1 = createSampleProduct 101 "Laptop" 1200.0m
171let product2 = createSampleProduct 102 "Mouse" 25.0m
172
173let orderWithItem1 = addItemToOrder order product1 1
174let orderWithItems =
175match orderWithItem1 with
176| Ok o -> addItemToOrder o product2 2
177| Error _ -> Error "Failed to add first item"
178
179match orderWithItems with
180| Ok finalOrder -> finalOrder
181| Error _ -> failwith "Error creating complex order"
182
183(*@
184Demonstrates adding an item to a non-pending order.
185*@)
186let demonstrateAddItemToNonPending () =
187let customer = createSampleCustomer 2 "Bob"
188let initialOrder = createOrder customer.Id customer.Name
189let product = createSampleProduct 201 "Keyboard" 75.0m
190
191match processOrder initialOrder with
192| Ok processedOrder ->
193match addItemToOrder processedOrder product 1 with
194| Ok _ -> printfn "Unexpected success adding item to processed order."
195| Error msg -> printfn "Correctly failed to add item: %s" msg
196| Error _ -> printfn "Failed to process order initially."
197
198(*@
199Final order creation and modification.
200*@)
201let finalOrderScenario () =
202let customer = createSampleCustomer 3 "Charlie"
203let order = createOrder customer.Id customer.Name
204let product = createSampleProduct 301 "Monitor" 300.0m
205match addItemToOrder order product 1 with
206| Ok updatedOrder -> updatedOrder
207| Error _ -> failwith "Failed to add item"
Algorithm description viewbox

F# Functional Domain Models: Order Processing

Algorithm description:

This F# code defines a functional domain model for an order processing system. It uses immutable records and discriminated unions to represent entities like `Order`, `Customer`, `Product`, and `OrderItem`. Functions like `createOrder` and `addItemToOrder` are pure, meaning they take an existing order and return a *new* order with the changes, preserving the original order's immutability. This approach enhances type safety and predictability.

Algorithm explanation:

The domain model is built using F#'s strong type system to ensure data integrity. `Customer`, `Product`, `OrderItem`, and `Order` are represented as immutable records. `OrderStatus` is a discriminated union, allowing for distinct states. The `createOrder` function initializes a new order with a random ID, customer details, an empty item list, `Pending` status, and a total price of 0. The `addItemToOrder` function is crucial: it takes an `Order` and returns a `Result<Order, string>`. This `Result` type elegantly handles potential errors (e.g., adding to a non-pending order, invalid quantity) without exceptions. If successful, it returns a *new* `Order` record with the added item and updated `TotalPrice`. The `TotalPrice` is calculated by summing the `Subtotal` of each `OrderItem`. The time complexity for adding an item is O(N) where N is the number of items already in the order, due to list concatenation (`@`). Space complexity is O(N) for the new list. Edge cases like invalid quantities and non-pending order statuses are explicitly handled by returning `Error`.

Pseudocode:

define types: CustomerId, ProductId, Product(Id, Name, Price), OrderItem(Product, Quantity, Subtotal), Customer(Id, Name), Order(Id, Customer, Items, Status, TotalPrice), OrderStatus(Pending, Processing, Shipped, Cancelled)

function createOrder(customerId, customerName):
  return new Order with Id=random, Customer={Id=customerId, Name=customerName}, Items=[], Status=Pending, TotalPrice=0

function addItemToOrder(order, product, quantity):
  if order.Status is not Pending or quantity <= 0:
    return Error
  else:
    calculate subtotal
    create new OrderItem
    create new Items list by appending new item
    calculate new TotalPrice
    return new Order with updated Items and TotalPrice

function calculateOrderTotal(order):
  return sum of subtotals of all items in order.Items