How can F # code be improved to be more idiomatic?

I have a windows service that reads email messages from a queue in a database using Entity Framework 5 and then sends them to an SMTP server using System.Net.Mail.SmtpClient

This is my first attempt to write an F # application, I'm from C # background. How can this be improved in order to be more functional and / or use the F # functions more fully? Could you explain what benefits these changes will make?

A worker is created by the service node, its work function is called when the service starts, and ContinueWorking is false when the service is stopped.

namespace EmailService open log4net open System open System.Linq open System.Net.Mail open EmailService.Context type Worker(contextFactory: EmailContextFactory, mailClient: ISmtpClient, logger: ILog) = let MapToMessage(email : Email) = let message = new MailMessage() message.Sender <- new MailAddress(email.From) message.From <- new MailAddress(email.From) message.Subject <- email.Subject message.Body <- email.Body message.IsBodyHtml <- email.IsBodyHtml message.To.Add(email.To) (email, message) member val ContinueWorking = true with get, set member this.Work() = logger.Info "Starting work" let mutable unsentEmails = Array.empty<Email> while this.ContinueWorking do use context = contextFactory.GetEntities() while this.ContinueWorking && Array.isEmpty unsentEmails do System.Threading.Thread.Sleep(1000) unsentEmails <- query { for q in context.QueueItems do where (q.Error = null) select q.Email } |> query.Take(10) |> query.toArray Array.map MapToMessage unsentEmails |> Array.iter (fun (email, message) -> try mailClient.SendMail(message) email.DateSent <- new Nullable<DateTime>(DateTime.UtcNow) context.QueueItems.Remove(email.QueueItem) |> ignore with | ex -> logger.Error(ex) email.QueueItem.Error <- ex.ToString()) context.SaveChanges() |> ignore logger.Info (sprintf "Sent %d emails" unsentEmails.Length) logger.Info "Work complete" 
+4
source share
1 answer

The Work method creates a long-term process that ultimately needs to be stopped. The best way to present this in F # would be to use asynchronous workflows - an asynchronous workflow can be paused without blocking the thread (so you don't need Thread.Sleep ), and it can be easily undone with the CancellationToken .

Otherwise, your code looks good to me. I made some minor changes (for example, use take in the F # query syntax, which is a small function).

I also don't quite understand why your code has two nested while . It's necessary? If not, I think you could write something like:

 member this.Work() = async { logger.Info "Starting work" while true do do! Async.Sleep(1000) use context = contextFactory.GetEntities() let unsentEmails = query { for q in context.QueueItems do where (q.Error = null) select q.Email take 10 } unsentEmails |> Array.map MapToMessage |> Array.iter (fun (email, message) -> try mailClient.SendMail(message) email.DateSent <- new Nullable<DateTime>(DateTime.UtcNow) context.QueueItems.Remove(email.QueueItem) |> ignore with ex -> logger.Error(ex) email.QueueItem.Error <- ex.ToString()) context.SaveChanges() |> ignore logger.Info (sprintf "Sent %d emails" unsentEmails.Length) logger.Info "Work complete" } 

To start the process (and stop it later), you should write something like:

 // Start the work let cts = new CancellationTokenSource() Async.Start(worker.Work(), cts.Token) // Stop the worker cts.Cancel() 
+5
source

Source: https://habr.com/ru/post/1447729/


All Articles