Friday, October 18, 2019

Easy Way To Become An F# Programmer For C# Developers

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.
 
Easy Way To Become An F# Programmer For C# Developers 
 
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.
 
Easy Way To Become An F# Programmer For C# Developers
 
The generated Program.fs,
  1. open System  
  2.   
  3. [<EntryPoint>]  
  4. let main argv =  
  5.   
  6. printfn "Hello World from F#!"  
  7. // 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,
  1. Install-Package SQLProvider -Version 1.1.68  
  2. Install-Package FSharp.Desktop.UI -Version 0.7.1 
Easy Way To Become An F# Programmer For C# Developers
 
Step 3
 
I have added the domain entity (Domain.fs) as below.

Customer class has two properties: CustomerId and Name.
  1. module Domain  
  2.   
  3. type Customer(customerId:int, name:string) =   
  4. inherit FSharp.Desktop.UI.Model()  
  5.   
  6. let mutable customerId =customerId   
  7. let mutable name = name  
  8.   
  9. member this.CustomerId  
  10. with get() = customerId  
  11. and set value =   
  12. customerId <- value  
  13. this.NotifyPropertyChanged "CustomerId"  
  14.   
  15. member this.Name  
  16. with get() = name  
  17. and set value =   
  18. name <- value  
  19. 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.
  1. module DataContext  
  2.   
  3. open FSharp.Data.Sql  
  4. open Microsoft.FSharp.Collections  
  5.   
  6. [<Literal>]  
  7. let connectionString = "Server=.\SQLExpress; Database=ShopDatabase;   
  8. Trusted_Connection=true;"  
  9.   
  10. type Sql = SqlDataProvider<Common.DatabaseProviderTypes.MSSQLSERVER, connectionString>  
  11.   
  12. let DbContext = Sql.GetDataContext()  
  13.   
  14. let FirstCustomer =   
  15.              query {  
  16.                        for customer in DbContext.Dbo.Customers do  
  17.                        select customer   
  18.                        } |> Seq.head;  
  19.   
  20. let GetNextCustomer (id) =   
  21.             query {  
  22.                       for customer in DbContext.Dbo.Customers do  
  23.                       where (customer.CustomerId >id)  
  24.                       select customer  
  25.                      } |> Seq.tryHead  
  26.   
  27. let GetBeforeCustomer (id) =  
  28.             query {  
  29.                       for customer in DbContext.Dbo.Customers do  
  30.                       where (customer.CustomerId < id)  
  31.                       select customer  
  32.                      } |> Seq.tryLast  
Step 5
 
I have created and seeded the database with the help of SQL Server Management Studio.
 
Easy Way To Become An F# Programmer For C# Developers 
 
The SQL Script,
  1. USE master;  
  2. GO  
  3.   
  4. IF DB_ID (N'ShopDatabase'IS NOT NULL  
  5. DROP DATABASE ShopDatabase;  
  6. GO  
  7.   
  8. CREATE DATABASE ShopDatabase;  
  9. GO  
  10.   
  11. Use ShopDatabase  
  12. GO  
  13.   
  14. CREATE TABLE Customers (  
  15. CustomerId INT PRIMARY KEY,  
  16. Name nvarchar(25));  
  17. GO  
  18.   
  19. INSERT INTO Customers(CustomerId, Name)  
  20. 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.”
 
Easy Way To Become An F# Programmer For C# Developers
 
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.
  1. module MainView  
  2.   
  3. open FSharp.Desktop.UI  
  4. open System.Windows  
  5. open System.Windows.Controls  
  6. open System.Windows.Input  
  7. open System.Windows.Data  
  8. open System.Windows.Media;  
  9.   
  10. type NumericUpDownEvents = Up | Down  
  11.   
  12. type MainView() as this =   
  13. inherit View<UpDownEvents, Domain.Customer, Window>(Window())   
  14.   
  15. //Assembling WPF window in code.   
  16. do   
  17. this.Root.Width <- 800.  
  18. this.Root.Height <- 600.  
  19. this.Root.WindowStartupLocation <- WindowStartupLocation.CenterScreen  
  20. this.Root.Title <- "F# Desktop Demo Application"  
  21. this.Root.Background <- Brushes.LightCyan  
  22.   
  23. let mainPanel =   
  24. let grid = Grid(HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, ShowGridLines = true)  
  25. [ RowDefinition(Height= GridLength(300.)); RowDefinition(Height= GridLength(300.)) ] |> List.iter grid.RowDefinitions.Add   
  26. [ ColumnDefinition(Width= GridLength(300.)); ColumnDefinition(Width= GridLength(300.));ColumnDefinition(Width= GridLength(200.)) ] |> List.iter grid.ColumnDefinitions.Add  
  27. grid  
  28.   
  29. let idLabel = Label(Content= "Id: ", FontSize = 20., Width = 150., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top);  
  30.   
  31. let userIdTextBox = TextBox(FontSize = 20., Width = 150., Height = 50., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Background = Brushes.LightBlue)  
  32.   
  33. let userNameLabel = Label(Content= "Name: ", FontSize = 20., Width = 150., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top)  
  34.   
  35. let userNameTextBox = TextBox(FontSize = 20., Width = 150., Height = 50., HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Background = Brushes.LightBlue)  
  36.   
  37. let upButton = Button(Content = "^", FontSize = 20., Width = 150., Height = 50., Background = Brushes.LightCyan)  
  38.   
  39. let downButton = Button(Content = "v", FontSize = 20.,Width = 150., Height = 50.,Background = Brushes.LightCyan)  
  40.   
  41. do   
  42. Grid.SetRow(idLabel,0)  
  43. Grid.SetColumn(idLabel,0)  
  44. Grid.SetRow(userIdTextBox,0)  
  45. Grid.SetColumn(userIdTextBox, 1)  
  46. Grid.SetRow(userNameLabel, 1)  
  47. Grid.SetColumn(userNameLabel, 0)  
  48. Grid.SetRow(userNameTextBox, 1)  
  49. Grid.SetColumn(userNameTextBox, 1)  
  50. Grid.SetRow(upButton, 0)  
  51. Grid.SetColumn(upButton, 2)  
  52. Grid.SetRow(downButton, 1)  
  53. Grid.SetColumn(downButton, 2)  
  54.   
  55. mainPanel.Children.Add idLabel |> ignore  
  56. mainPanel.Children.Add userIdTextBox |> ignore  
  57. mainPanel.Children.Add userNameLabel |> ignore  
  58. mainPanel.Children.Add userNameTextBox |> ignore  
  59. mainPanel.Children.Add upButton |> ignore  
  60. mainPanel.Children.Add downButton |> ignore  
  61.   
  62. this.Root.Content <- mainPanel  
  63.   
  64. //View implementation   
  65. override this.EventStreams = [  
  66. upButton.Click |> Observable.map (fun _ -> Up)  
  67. downButton.Click |> Observable.map (fun _ -> Down)  
  68. userIdTextBox.KeyUp |> Observable.choose (fun args ->   
  69. match args.Key with   
  70. | Key.Up -> Some Up   
  71. | Key.Down -> Some Down  
  72. | _ -> None  
  73. )  
  74. userIdTextBox.MouseWheel |> Observable.map (fun args -> if args.Delta > 0 then Up else Down)  
  75. ]  
  76. override this.SetBindings model =   
  77. Binding.OfExpression   
  78. <@  
  79. userIdTextBox.Text <- coerce model.CustomerId   
  80. userNameTextBox.Text <- coerce model.Name   
  81. @>  
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.
  1. module Controller  
  2.   
  3. let eventHandler event (customer: Domain.Customer) =  
  4.               match event with  
  5. | MainView.Up ->   
  6. let nextCustomer = DataContext.GetNextCustomer customer.CustomerId  
  7.               match nextCustomer with   
  8.               | Some c -> customer.CustomerId <- c.CustomerId  
  9.                                 customer.Name <- c.Name   
  10.               | None -> customer.CustomerId <- 0  
  11.                              customer.Name <- "Not found upper!"   
  12.   
  13. | MainView.Down ->  
  14. let beforeCustomer = DataContext.GetBeforeCustomer customer.CustomerId  
  15.               match beforeCustomer with   
  16.             | Some c -> customer.CustomerId <- c.CustomerId  
  17.                               customer.Name <- c.Name   
  18.             | None -> customer.CustomerId <- 0  
  19.                            customer.Name <- "Not found down!"
Step 8
 
Finally, we have to wire the modules. I did that in the Program.fs
  1. open System  
  2. open System.Windows  
  3. open FSharp.Desktop.UI  
  4.   
  5. [<STAThread>]  
  6. // Learn more about F# at http://fsharp.org  
  7.   
  8. [<EntryPoint>]  
  9. do  
  10. let firstCustomer = DataContext.FirstCustomer  
  11.   
  12. // Create a Customer Model instance.  
  13. let customerModel = Domain.Customer.Create(firstCustomer.CustomerId, firstCustomer.Name)  
  14.   
  15. let view = MainView.MainView()  
  16.   
  17. let controller:IController<MainView.UpDownEvents, Domain.Customer> = Controller.Create Controller.eventHandler  
  18.   
  19. let mvc = Mvc(customerModel, view,controller)  
  20. use eventLoop = mvc.Start()  
  21. Application().Run( window = view.Root) |> ignore  
Your application should be like below,
 
Easy Way To Become An F# Programmer For C# Developers
 
Press F5 and enjoy your first F# application😊
 
Easy Way To Become An F# Programmer For C# Developers
 
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.
  1. let mutable customerId =customerId   
  2. 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.
  1. let FirstCustomer = query {  
  2.     for customer in DbContext.Dbo.Customers do  
  3.     select customer   
  4. } |> Seq.head;

No comments:

Post a Comment

No String Argument Constructor/Factory Method to Deserialize From String Value

  In this short article, we will cover in-depth the   JsonMappingException: no String-argument constructor/factory method to deserialize fro...