Created
December 27, 2019 05:20
-
-
Save airicbear/92affa00e60fdd74ef07b6b4b80add91 to your computer and use it in GitHub Desktop.
Tour of F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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