F# 8: Unwrapping New Features and Nostalgic Connections
Explore F# 8's exciting new features including simplified lambda syntax and nested record updates. Learn how modern F# compares to nostalgic library approaches.
Ah, F#—the language that keeps surprising us! The recent release of F# 8 brings a bag full of goodies that not only enhance the language but also make us chuckle at the memories. It's like the F# team is constantly pulling rabbits out of their hats.
You can dive into the extensive list of changes in the official announcement blog post, but let's take a stroll down memory lane and see how some of these updates echo with libraries from the past.
The "Fun" Removal
One of the notable changes is the removal of the need to write fun in default cases. It not only improves readability but also, alas, takes away the popular joke among F# enthusiasts that F# is "fun" to use—a small price to pay for clarity.
Before
type Person = {Name : string; Age : int}
let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
let beforeThisFeature =
people
|> List.distinctBy (fun x -> x.Name)
|> List.groupBy (fun x -> x.Age)
|> List.map (fun (x,y) -> y)
|> List.map (fun x -> x.Head.Name)
|> List.sortBy (fun x -> x.ToString())
After
type Person = {Name : string; Age : int}
let people = [ {Name = "Joe"; Age = 20} ; {Name = "Will"; Age = 30} ; {Name = "Joe"; Age = 51}]
let possibleNow =
people
|> List.distinctBy _.Name
|> List.groupBy _.Age
|> List.map snd
|> List.map _.Head.Name
|> List.sortBy _.ToString()
It's not just F# that's playing with "fun"—check out FSharp.Core.Fluent. Sure, the library might be taking a nap, but when it was awake, it was doing similar things with dots!
open FSharp.Core.Fluent
let xs = [ 1 .. 10 ]
xs.map(fun x -> x + 1).filter(fun x -> x > 4).sort()
xs.map(fun x -> x + 1)
.filter(fun x -> x > 4)
.sort()
No more pipes, but hey, dots are in!
Nested Records
Nested record updates used to be a workout, but not anymore. F# 8 comes to the rescue with concise syntax.
Before
type SteeringWheel = { Type: string }
type CarInterior = { Steering: SteeringWheel; Seats: int }
type Car = { Interior: CarInterior; ExteriorColor: string option }
let beforeThisFeature x =
{ x with Interior = { x.Interior with
Steering = {x.Interior.Steering with Type = "yoke"}
Seats = 5
}
}
After
let withTheFeature x = { x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }
What was there before? Ah, the legendary Lens from the timeless fsharpplus library. It's the hero that handled nested records way before it was cool.
open System
open FSharpPlus
// In order to use the Lens module of F#+, we import the following:
open FSharpPlus.Lens
// From Mauricio Scheffer: https://gist.github.com/mausch/4260932
type Person =
{ Name: string
DateOfBirth: DateTime }
module Person =
let inline _name f p =
f p.Name <&> fun x -> { p with Name = x }
type Page =
{ Contents: string }
module Page =
let inline _contents f p =
f p.Contents <&> fun x -> {p with Contents = x}
type Book =
{ Title: string
Author: Person
Pages: Page list }
module Book =
let inline _author f b =
f b.Author <&> fun a -> { b with Author = a }
let inline _authorName b = _author << Person._name <| b
let inline _pages f b =
f b.Pages <&> fun p -> { b with Pages = p }
let inline _pageNumber i b =
_pages << List._item i << _Some <| b
let rayuela =
{ Book.Title = "Rayuela"
Author = { Person.Name = "Julio Cortázar"
DateOfBirth = DateTime(1914, 8, 26) }
Pages = [
{ Contents = "Once upon a time" }
{ Contents = "The End"} ] }
// read book author name:
let authorName1 = view Book._authorName rayuela
// you can also write the read operation as:
let authorName2 = rayuela ^. Book._authorName
// write value through a lens
let book1 = setl Book._authorName "William Shakespear" rayuela
// update value
let book2 = over Book._authorName String.toUpper rayuela
The symbols in fsharpplus were lifesavers, handling nested records like a boss.
Wrapping It Up
These updates in F# 8 aren't just features; they're nostalgic nods to libraries that paved the way. Kudos to the F# team and the brilliant minds behind these libraries from the past.
This birthday post is part of the FsAdvent calendar 2023. F# turns another year older with me, and it only gets better.
Frequently Asked Questions
Removing the need to write 'fun' in default cases improves code readability and conciseness. Instead of writing 'fun x -> x.Name', you can now use the shorter underscore syntax '_.Name', making your code cleaner and easier to follow while maintaining the same functionality.
F# 8 simplifies nested record updates with dot notation syntax. Instead of chaining multiple 'with' statements, you can now directly access and update nested properties like '{ x with Interior.Steering.Type = "yoke"; Interior.Seats = 5 }', making the syntax much more concise and readable.
Libraries like FSharp.Core.Fluent and fsharpplus's Lens module previously provided functionality that F# 8 has now incorporated natively. For example, Lens handled nested record updates before F# 8's built-in support, while FSharp.Core.Fluent offered fluent API patterns with dot notation.
Yes, the removal of 'fun' is optional—the new underscore syntax (_.PropertyName) is an alternative, more concise way to write lambda expressions. You can continue using the traditional 'fun x -> x.PropertyName' syntax if you prefer, as both approaches are supported.
Yes, the underscore syntax works seamlessly with standard F# list operations like List.map, List.filter, List.distinctBy, and List.groupBy. It provides a cleaner alternative to lambda expressions in any context where you're accessing properties or methods on function parameters.