The basis for Object-Oriented Programming languages started in the early 1960s, and for over 30 years, it has dominated the market. Over time, many problems in the Object-Oriented Programming paradigm have been found.
This article is not to describe the OOP problems, but I have quoted a text from Dijkstra. This Dijkstra quote should be the key for you to understand the OOP problems.
Functional Programming (FP) is another programming paradigm, and it exists even before Object-Oriented Programming. FP has a strong mathematical foundation based on lambda calculations.
The one thing that functional programming does well is it helps us write reliable software, and the need for a debugger almost disappears.
Functional programming uses abstract mathematics (algebra, logic); and, if you do everything pure and clean, then you can even write a mathematical proof that your source code fulfills a specific formal specification.
I am like most of you, an imperative programmer. The problem is, I have learned functional programming concepts several times, but by the time I need it I have forgotten the most stuff because I do not practice it.
I have decided to learn Functional Programming in an innovative way. I will use a non-pure functional programming language, and I will create a desktop business domain application just like what we do with C#, and step by step, convert it to a clean functional style. In this way, I will not forget what I did, and I can also use it in my daily job.
In this article, I have written my experience with F#, and I hope this article can motivate you to write more functional code.
“F# is a simple and expressive programming language. It can be described as statically typed impure functional language that supports functional, imperative, and object-oriented paradigm and also several other programming styles, including data-driven, event-driven, and parallel programming.”
The Customers Demo application.
Source Code on Github.
Or you can download it from here.
Prerequisite
Visual Studio 2019 and Microsoft SQL Server Express
Step 1
I have created an F# Visual Studio project as below.
The generated Program.fs,
- open System
- [<EntryPoint>]
- let main argv =
- printfn "Hello World from F#!"
- 0 // return an integer exit code
If you press F5, then you can see the Hello World from F#! in the console.
Step 2
I have added two libraries — one to access the database and the other one for the user interface based on WPF.
In the Package Manager Console,
- Install-Package SQLProvider -Version 1.1.68
- Install-Package FSharp.Desktop.UI -Version 0.7.1
Step 3
I have added the domain entity (Domain.fs) as below.
Customer class has two properties: CustomerId and Name.
- module Domain
- type Customer(customerId:int, name:string) =
- inherit FSharp.Desktop.UI.Model()
- let mutable customerId =customerId
- let mutable name = name
- member this.CustomerId
- with get() = customerId
- and set value =
- customerId <- value
- this.NotifyPropertyChanged "CustomerId"
- member this.Name
- with get() = name
- and set value =
- name <- value
- this.NotifyPropertyChanged "Name"
Step 4
I need to store/load the customer to/from the database, so I have defined my database context in the DataContext.
- module DataContext
- open FSharp.Data.Sql
- open Microsoft.FSharp.Collections
- [<Literal>]
- let connectionString = "Server=.\SQLExpress; Database=ShopDatabase;
- Trusted_Connection=true;"
- type Sql = SqlDataProvider<Common.DatabaseProviderTypes.MSSQLSERVER, connectionString>
- let DbContext = Sql.GetDataContext()
- let FirstCustomer =
- query {
- for customer in DbContext.Dbo.Customers do
- select customer
- } |> Seq.head;
- let GetNextCustomer (id) =
- query {
- for customer in DbContext.Dbo.Customers do
- where (customer.CustomerId >id)
- select customer
- } |> Seq.tryHead
- let GetBeforeCustomer (id) =
- query {
- for customer in DbContext.Dbo.Customers do
- where (customer.CustomerId < id)
- select customer
- } |> Seq.tryLast
Step 5
I have created and seeded the database with the help of SQL Server Management Studio.
The SQL Script,
- USE master;
- GO
- IF DB_ID (N'ShopDatabase') IS NOT NULL
- DROP DATABASE ShopDatabase;
- GO
- CREATE DATABASE ShopDatabase;
- GO
- Use ShopDatabase
- GO
- CREATE TABLE Customers (
- CustomerId INT PRIMARY KEY,
- Name nvarchar(25));
- GO
- INSERT INTO Customers(CustomerId, Name)
- VALUES (1, 'Bassam'), (2,'Mays'), (3,'Rami'), (4,'Fadi'), (5,'Alugili');
Step 6
I have used FSharp.Desktop.UI to display the data.
„FSharp.Desktop.UI designed for building WPF applications in F#. With strong support for MVC, functional, asynchronous, and event-driven programming, it will enable you to build your solution quickly, without the need to sacrifice type system or testability.”
The main view contains the view logic. I have added in the grid the main panel and two textboxes and two buttons and some labels, and I have also registered some events like Button Click/ MouseWheel / Key Up/Down.
- module MainView
- open FSharp.Desktop.UI
- open System.Windows
- open System.Windows.Controls
- open System.Windows.Input
- open System.Windows.Data
- open System.Windows.Media;
- type NumericUpDownEvents = Up | Down
- type MainView() as this =
- inherit View<UpDownEvents, Domain.Customer, Window>(Window())
- //Assembling WPF window in code.
- do
- this.Root.Width <- 800.
- this.Root.Height <- 600.
- this.Root.WindowStartupLocation <- WindowStartupLocation.CenterScreen
- this.Root.Title <- "F# Desktop Demo Application"
- this.Root.Background <- Brushes.LightCyan
- let mainPanel =
- let grid = Grid(HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, ShowGridLines = true)
- [ RowDefinition(Height= GridLength(300.)); RowDefinition(Height= GridLength(300.)) ] |> List.iter grid.RowDefinitions.Add
- [ ColumnDefinition(Width= GridLength(300.)); ColumnDefinition(Width= GridLength(300.));ColumnDefinition(Width= GridLength(200.)) ] |> List.iter grid.ColumnDefinitions.Add
- grid
- let idLabel = Label(Content= "Id: ", FontSize = 20., Width = 150., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top);
- let userIdTextBox = TextBox(FontSize = 20., Width = 150., Height = 50., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Background = Brushes.LightBlue)
- let userNameLabel = Label(Content= "Name: ", FontSize = 20., Width = 150., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top)
- let userNameTextBox = TextBox(FontSize = 20., Width = 150., Height = 50., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Background = Brushes.LightBlue)
- let upButton = Button(Content = "^", FontSize = 20., Width = 150., Height = 50., Background = Brushes.LightCyan)
- let downButton = Button(Content = "v", FontSize = 20.,Width = 150., Height = 50.,Background = Brushes.LightCyan)
- do
- Grid.SetRow(idLabel,0)
- Grid.SetColumn(idLabel,0)
- Grid.SetRow(userIdTextBox,0)
- Grid.SetColumn(userIdTextBox, 1)
- Grid.SetRow(userNameLabel, 1)
- Grid.SetColumn(userNameLabel, 0)
- Grid.SetRow(userNameTextBox, 1)
- Grid.SetColumn(userNameTextBox, 1)
- Grid.SetRow(upButton, 0)
- Grid.SetColumn(upButton, 2)
- Grid.SetRow(downButton, 1)
- Grid.SetColumn(downButton, 2)
- mainPanel.Children.Add idLabel |> ignore
- mainPanel.Children.Add userIdTextBox |> ignore
- mainPanel.Children.Add userNameLabel |> ignore
- mainPanel.Children.Add userNameTextBox |> ignore
- mainPanel.Children.Add upButton |> ignore
- mainPanel.Children.Add downButton |> ignore
- this.Root.Content <- mainPanel
- //View implementation
- override this.EventStreams = [
- upButton.Click |> Observable.map (fun _ -> Up)
- downButton.Click |> Observable.map (fun _ -> Down)
- userIdTextBox.KeyUp |> Observable.choose (fun args ->
- match args.Key with
- | Key.Up -> Some Up
- | Key.Down -> Some Down
- | _ -> None
- )
- userIdTextBox.MouseWheel |> Observable.map (fun args -> if args.Delta > 0 then Up else Down)
- ]
- override this.SetBindings model =
- Binding.OfExpression
- <@
- userIdTextBox.Text <- coerce model.CustomerId
- userNameTextBox.Text <- coerce model.Name
- @>
Step 7
I have created a controller to separate the presentation logic from the business logic — the trick here in the UpDownEvents. When any button or key is pressed or you move the mouse wheel up/down in the Textbox, then I am firing in the View an up or down event. I am listing those events in the controller and retrieving the data from the database, as shown below.
- module Controller
- let eventHandler event (customer: Domain.Customer) =
- match event with
- | MainView.Up ->
- let nextCustomer = DataContext.GetNextCustomer customer.CustomerId
- match nextCustomer with
- | Some c -> customer.CustomerId <- c.CustomerId
- customer.Name <- c.Name
- | None -> customer.CustomerId <- 0
- customer.Name <- "Not found upper!"
- | MainView.Down ->
- let beforeCustomer = DataContext.GetBeforeCustomer customer.CustomerId
- match beforeCustomer with
- | Some c -> customer.CustomerId <- c.CustomerId
- customer.Name <- c.Name
- | None -> customer.CustomerId <- 0
- customer.Name <- "Not found down!"
Step 8
Finally, we have to wire the modules. I did that in the Program.fs
- open System
- open System.Windows
- open FSharp.Desktop.UI
- [<STAThread>]
- // Learn more about F# at http://fsharp.org
- [<EntryPoint>]
- do
- let firstCustomer = DataContext.FirstCustomer
- // Create a Customer Model instance.
- let customerModel = Domain.Customer.Create(firstCustomer.CustomerId, firstCustomer.Name)
- let view = MainView.MainView()
- let controller:IController<MainView.UpDownEvents, Domain.Customer> = Controller.Create Controller.eventHandler
- let mvc = Mvc(customerModel, view,controller)
- use eventLoop = mvc.Start()
- Application().Run( window = view.Root) |> ignore
Your application should be like below,
Press F5 and enjoy your first F# application😊
You can click on the up/down buttons to browse the data, or you can do that by using the arrow keys up/down or the Mouse wheel.
The application is ready to use. The next question is, what we can do it better in this application?
The first problem: We have used the mutable keyword.
- let mutable customerId =customerId
- let mutable name = name
The mutable state belongs to the imperative programming style. Can we change that?
The second problem: This query throws an exception if the database does not exist or is empty. Exceptions in functional programming are not desired. Can we make it better? A tip looks to tryHead.
- let FirstCustomer = query {
- for customer in DbContext.Dbo.Customers do
- select customer
- } |> Seq.head;
No comments:
Post a Comment