Skip to content

Instantly share code, notes, and snippets.

@airicbear
Created December 27, 2019 05:20
Show Gist options
  • Save airicbear/92affa00e60fdd74ef07b6b4b80add91 to your computer and use it in GitHub Desktop.
Save airicbear/92affa00e60fdd74ef07b6b4b80add91 to your computer and use it in GitHub Desktop.
Tour of F#
// Learn more about F# at http://fsharp.org
open System
module BasicFunctions =
// Use 'let' to define a function
// Parenthesis are optional except for typed arguments
let sampleFunction1 x = x * x + 3
let result1 = sampleFunction1 4573
// Annotate type of a parameter using '(argument:type)'. Parenthesis are required.
let sampleFunction2 (x:int) = 2 * x * x - x / 5 + 3
let result2 = sampleFunction2 (7 + 4)
// Conditionals use if/then/elif/else
// F# use white space indentation-aware syntax similar to Python
let sampleFunction3 x =
if x < 100.0 then
2.0 * x * x - x / 5.0 + 3.0
else
2.0 * x * x - x / 5.0 - 37.0
let result3 = sampleFunction3 (6.5 + 4.5)
module Immutability =
let number = 2
// let number = 3 // this will not work
let mutable otherNumber = 2
otherNumber <- otherNumber + 1
module IntegersAndNumbers =
let sampleInteger = 176
let sampleDouble = 4.1
let sampleInteger2 = (sampleInteger / 4 + 5 - 7) * 4 + int sampleDouble
let sampleNumbers = [ 0 .. 99 ]
let sampleTableOfSquares = [ for i in sampleNumbers -> (i, i * i) ]
module Booleans =
let boolean1 = true
let boolean2 = false
let boolean3 = not boolean1 && (boolean2 || false)
module StringManipulation =
let string1 = "Hello"
let string2 = "world"
// Strings can use '@' or triple-quotes to create verbatim string literals
// This will ignore escape characters such as '\', '\n', '\t', etc.
let string3 = @"C:\Program Files\"
let string4 = """The computer said "hello world" when I told it to!"""
// String concatenation is normally done with the '+' operator.
let helloWorld = string1 + " " + string2
// Substrings use the indexer notation.
let substring = helloWorld.[0..6]
module Tuples =
let tuple1 = (1, 2, 3)
// Swap two values in a tuple.
// Type inference applies, automatically generalizing the function.
let swapElems (a, b) = (b, a)
let tuple2 = (1, "fred", 3.1415)
// Tuples are normally objects, but can be represented as structs
// F# structs interoperate with structs in C# and VB.NET.
// struct tuples are not implicity convertible with object tuples (often called reference tuples)
let sampleStructTuple = struct (1, 2)
// let thisWillNotCompile: (int*int) = struct (1, 2)
let convertFromStructTuple (struct(a, b)) = (a, b)
let convertToStructTuple (a, b) = struct(a, b)
module PipelinesAndComposition =
let square x = x * x
let addOne x = x + 1
// The '<>' operator is used to test inequality
let isOdd x = x % 2 <> 0
let numbers = [ 1; 2; 3; 4; 5 ]
let squareOddValuesAndAddOne values =
let odds = List.filter isOdd values
let squares = List.map square odds
let result = List.map addOne squares
result
// Shorter version of previous function
let squareOddValeusAndAddOneNested values =
List.map (addOne >> square) (List.filter isOdd values)
let squareOddValuesAndAddOnePipeline values =
values
|> List.filter isOdd
|> List.map (square >> addOne)
// Using the lambda function
let squareOddValuesAndAddOneLambdaPipeline values =
values
|> List.filter isOdd
|> List.map(fun x -> x |> (square >> addOne))
module Lists =
// Empty list
let list1 = [ ]
// Use ';' or new lines to separate elements
let list2 = [ 1; 2; 3 ]
let list3 = [
1
2
3
]
// List of integers from 1 to 1000
let numberList = [ 1 .. 1000 ]
// Generated list
let daysList =
[ for month in 1 .. 12 do
for day in 1 .. System.DateTime.DaysInMonth(2019, month) do
yield System.DateTime(2019, month, day) ]
// Generated list with conditionals
let blackSquares =
[ for i in 0 .. 7 do
for j in 0 .. 7 do
if (i + j) % 2 = 1 then
yield (i, j) ]
// You can transform lists using 'List.map' and other functional programming combinators.
let squares =
numberList
|> List.map (fun x -> x * x)
let sumOfSquares =
numberList
|> List.filter (fun x -> x % 3 = 0)
|> List.sumBy (fun x -> x * x)
module Arrays =
let array1 = [| |]
let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]
let array3 = [| 1 .. 1000 |]
let array4 =
[| for word in array2 do
if word.Contains("l") then
yield word |]
let evenNumbers = Array.init 1001 (fun n -> n * 2)
let evenNumbersSlice = evenNumbers.[0..10]
let loopArray array =
for word in array do
printfn "word: %s" word
array2.[1] <- "WORLD!"
let sumOfLengthOfWords =
array2
|> Array.filter (fun x -> x.StartsWith "h")
|> Array.sumBy (fun x -> x.Length)
module Sequences =
let seq1 = Seq.empty
let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }
let numbersSeq = seq { 1 .. 1000 }
let seq3 =
seq { for word in seq2 do
if word.Contains("l") then
yield word }
let evenNumbers = Seq.init 1001 (fun n -> n * 2)
let rnd = System.Random()
let rec randomWalk x =
seq { yield x
yield! randomWalk (x + rnd.NextDouble() - 0.5) }
let first100ValuesOfRandomWalk =
randomWalk 5.0
|> Seq.truncate 100
|> Seq.toList
module RecursiveFunctions =
let rec factorial n =
if n = 0 then 1 else n * factorial (n - 1)
// The compiler turns this into a loop since the function is tail recursive.
let rec greatestCommonFactor a b =
if a = 0 then b
elif a < b then greatestCommonFactor a (b - a)
else greatestCommonFactor (a - b) b
// 'match' is basically a Switch statement.
// '::' is a prepending operator.
let rec sumList xs =
match xs with
| [] -> 0
| y::ys -> y + sumList ys
// This makes 'sumList' tail recursive, using a helper function with a result accumulator.
let rec private sumListTailRecHelper accumulator xs =
match xs with
| [] -> accumulator
| y::ys -> sumListTailRecHelper (accumulator + y) ys
// This invokes the tail recursive helper function, providing '0' as a seed accumulator.
// An approach like this is common in F#.
let sumListTailRecursive xs = sumListTailRecHelper 0 xs
module RecordTypes =
// This example shows how to define a new record type.
type ContactCard =
{ Name : string
Phone : string
Verified : bool }
// This example shows how to instantiate a record type.
let contact1 =
{ Name = "Alf"
Phone = "(206) 555-0157"
Verified = false }
// You can also do this on the same line with ';' separators.
let contactOnSameLine = { Name = "Alf"; Phone = "(206) 555-0157"; Verified = false }
// How to "copy-and-update" record values.
let contact2 =
{ contact1 with
Phone = "(206) 555-0112"
Verified = true }
// A function that processes a record value.
let showContactCard (c: ContactCard) =
c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")
// This is an example of a Record with a member.
type ContactCardAlternate =
{ Name : string
Phone : string
Address : string
Verified : bool}
// Members can implement object-oriented members.
member this.PrintedContactCard =
this.Name + " Phone: " + this.Phone + (if not this.Verified then " (unverified)" else "") + this.Address
let contactAlternate =
{ Name = "Alf"
Phone = "(206) 555-0157"
Verified = false
Address = "111 Alf Street" }
// Records can be represented as structs via the 'Struct' attribute
// This is helpful when the performance of structs outweighs the
// flexibility of reference types
[<Struct>]
type ContactCardStruct =
{ Name : string
Phone : string
Verified : bool }
module DiscriminatedUnions =
// The following represents the suit of a playing card.
type Suit =
| Hearts
| Clubs
| Diamonds
| Spades
// A Discriminated Union can also be used to represent the rank of a playing card.
type Rank =
// Represents the rank of cards 2 .. 10
| Value of int
| Ace
| King
| Queen
| Jack
// Discriminated Unions can also implement object-oriented members.
static member GetAllRanks() =
[ yield Ace
for i in 2 .. 10 do yield Value i
yield Jack
yield Queen
yield King ]
// This is a record type that combines a Sui and a Rank.
// It's common to use both Records and Discriminated Unions when representing data.
type Card = { Suit: Suit; Rank: Rank }
// This computes a list representing all the cards in the deck.
let fullDeck =
[ for suit in [ Hearts; Diamonds; Clubs; Spades ] do
for rank in Rank.GetAllRanks() do
yield { Suit=suit; Rank=rank } ]
// This example converts a 'Card' object to a string.
let showPlayingCard (c: Card) =
let rankString =
match c.Rank with
| Ace -> "Ace"
| King -> "King"
| Queen -> "Queen"
| Jack -> "Jack"
| Value n -> string n
let suitString =
match c.Suit with
| Clubs -> "clubs"
| Diamonds -> "diamonds"
| Spades -> "spades"
| Hearts -> "hearts"
rankString + " of " + suitString
// This example prints all the cards in a playing deck.
let printAllCards() =
for card in fullDeck do
printfn "%s" (showPlayingCard card)
// Single-case DUs are often used for domain modeling.
// This can buy you extra type safety over primitives.
//
// For example, a function that takes an Address cannot accept a string primitive.
// It must use an Address type.
type Address = Address of string
type Name = Name of string
type SSN = SSN of int
// You can easily instantiate a single-case DU as follows.
let address = Address "111 Alf Way"
let name = Name "Alf"
let ssn = SSN 1234567890
// When you need the value, you can unwrap the underlying value with a simple function.
let unwrapAddress (Address a) = a
let unwrapName (Name n) = n
let unwrapSSN (SSN s) = s
let printSingleCaseDUs() =
printfn "Address: %s, Name: %s, and SSN: %d" (address |> unwrapAddress) (name |> unwrapName) (ssn |> unwrapSSN)
// DUs support recursive functions
//
// This represents a Binary Search Tree, with one case being the Empty tree,
// and the other being a Node with a value and two subtrees.
type BST<'T> =
| Empty
| Node of value:'T * left: BST<'T> * right: BST<'T>
// Check if an item exists in the binary search tree.
// Searches recursively using Pattern Matching. Returns true if it exists; otherwise, false.
let rec exists item bst =
match bst with
| Empty -> false
| Node (x, left, right) ->
if item = x then true
elif item < x then (exists item left)
else (exists item right)
// Inserts an item in the Binary Search Tree.
let rec insert item bst =
match bst with
| Empty -> Node(item, Empty, Empty)
| Node(x, left, right) as node ->
if item = x then node
elif item < x then Node(x, insert item left, right)
else Node(x, left, insert item right)
// DUs can be represented as structs via the 'Struct' attribute.
// This is similar to how Records can be represented as structs.
//
// Note that:
// 1. A struct DU cannot be recursively-defined.
// 2. A struct DU must have unique names for each of its cases.
[<Struct>]
type Shape =
| Circle of radius: float
| Square of side: float
| Triangle of height: float * width: float
module PatternMatching =
type Person = {
First : string
Last : string
}
type Employee =
| Engineer of engineer: Person
| Manager of manager: Person * reports: List<Employee>
| Executive of executive: Person * reports: List<Employee> * assistant: Employee
let rec countReports(emp : Employee) =
1 + match emp with
| Engineer(person) ->
0
| Manager(person, reports) ->
reports |> List.sumBy countReports
| Executive(person, reports, assistant) ->
(reports |> List.sumBy countReports) + countReports assistant
// Find all managers/executives named "Dave" who do not have any erports.
let rec findDaveWithOpenPosition(emps : List<Employee>) =
emps
|> List.filter(function
| Manager({First = "Dave"}, []) -> true
| Executive({First = "Dave"}, [], _) -> true
| _ -> false)
// The following code is commented due to errors [F# 4.7]
//
// Active patterns allow you to partition input data into custom forms.
// let (|Int|_|) = parseInt
// let (|Double|_|) = parseDouble
// let (|Date|_|) = parseDateTimeOffset
// let (|TimeSpan|_|) = parseTimeSpan
// let printParseResult = function
// | Int x -> printfn "%d" x
// | Double x -> printfn "%f" x
// | Date d -> printfn "%s" (d.ToString())
// | TimeSpan t -> printfn "%s" (t.ToString())
// | _ -> printfn "Nothing was parse-able!"
// Option values are any kind of value tagged with either 'Some' or 'None'
// They are used extensively in F# code to represent the cases where many other
// languages would use null references.
module OptionValues =
// Define a Single-case DU
type ZipCode = ZipCode of string
// Define when it is optional
type Customer = { ZipCode: ZipCode option }
// Define an interface type that represents an object to compute the shipping zone for the customer's zip code,
// given implementations for the 'getState' and 'getShippingZone' abstract methods.
type IShippingCalculator =
abstract GetState : ZipCode -> string option
abstract GetShippingZone : string -> int
// Calculate a shipping zone for a customer using a calculator instance.
// This uses combinators in the Option module to allow a functional pipeline
// for transforming data with Optionals.
let CustomerShippingZone (calculator: IShippingCalculator, customer: Customer) =
customer.ZipCode
|> Option.bind calculator.GetState
|> Option.map calculator.GetShippingZone
// Units of measure are a way to annotate primitive numeric types in a type-safe way.
// You can then perform type-safe arithmetic on these values.
module UnitsOfMeasure =
// First, open a collection of common unit names
open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
// Define a unitized constant
let sampleValue1 = 1600.0<meter>
// Next, define a new unit type
[<Measure>]
type mile =
// Conversion factor mile to meter
static member asMeter = 1609.34<meter/mile>
// Define a unitized constant
let sampleValue2 = 500.0<mile>
// Compute metric-system constant
let sampleValue3 = sampleValue2 * mile.asMeter
let printExample() =
printfn "After a %f race I would walk %f miles which would be %f meters" sampleValue1 sampleValue2 sampleValue3
// Classes are a way of defining new object types in F#, and support standard Object-oriented constructs.
// They have a variety of members (methods, properties, events, etc.)
module DefiningClasses =
// A simple two-dimensional vector class.
//
// The class's constructor is on the first line,
// and takes two arguments: dx and dy, both of type 'double'.
type Vector2D(dx : double, dy : double) =
// This internal field stores the length of the vector, computed when the
// object is constructed
let length = sqrt ((dx * dx) + (dy * dy))
// 'this' specifies a name for the object's self-identifier.
// In instance methods, it must appear before the member name.
member this.DX = dx
member this.DY = dy
member this.Length = length
// This member is a method. The previous members were properties.
member this.Scale(k) = Vector2D(k * this.DX, k * this.DY)
// This is how you instantiate the Vector2D class.
let vector1 = Vector2D(3.0, 4.0)
// Get a new scaled vector object, without modifying the original object
let vector2 = vector1.Scale(10.0)
let printExample() =
printfn "Length of vector1: %f\nLength of vector2: %f" vector1.Length vector2.Length
// Generic classes allow types to be defined with respect to a set of type parameters.
// In the following, 'T is the type parameter for the class.
module DefiningGenericClasses =
type StateTracker<'T>(initialElement: 'T) =
// This internal field store the states in a list.
let mutable states = [ initialElement ]
// Add a new element to the list of states.
member this.UpdateState newState =
states <- newState :: states // use the '<-' operator to mutate the value
// Get the entire list of historical states.
member this.History = states
// Get the latest state.
member this.Current = states.Head
// An 'int' instacne of the state tracker class. Note that the type parameter is inferred.
let tracker = StateTracker 10
// Add a state
tracker.UpdateState 17
let printExample() =
printfn "%d" tracker.Current
// Interfaces are object types with only 'abstract' members.
// Object types and object expressions can implement interfaces.
module ImplementingInterfaces =
// This is a type that implements IDisposable.
type ReadFile() =
let file = new System.IO.StreamReader("readme.txt")
member this.ReadLine() = file.ReadLine()
// This is the implementation of IDisposable members.
interface System.IDisposable with
member this.Dispose() = file.Close()
// This is an object that implements IDisposable via an Object Expression.
// Unlike other languages such as C# or Java, a new type definition is not needed
// to implement an interface.
let interfaceImplementation =
{ new System.IDisposable with
member this.Dispose() = printfn "disposed" }
[<EntryPoint>]
let main argv =
DefiningGenericClasses.printExample()
0 // return an integer exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment