(Video and more at fsharpforfunandprofit.com/csharp)
Curious about F# and want to understand how is it different from C#?
In this talk, we'll look at the basics of coding in F#, and how functional programming differs from object-oriented programming. Along the way, there will be many examples showing the same code written in C# and F# so that you can see for yourself how the two languages differ in style and approach.
1. F# for C# programmers
@ScottWlaschin
fsharpforfunandprofit.com
2. A language that doesn't affect the way you
think about programming, is not worth
knowing – Alan Perlis
3. “F# is slightly better than C#,
but not so much that it's
really worth the effort to
move towards it.”
“Learning F# was one of the biggest
boosts in my career as a C# developer.
I don't use it professionally (I wish I
could) but this knowledge made me a
better programmer.”
4. About F#
• Developed by Microsoft Research
– Shipped withVisual Studio in 2010
• Open source
– On github
• Cross platform
– Works withVS Code (and other editors)
• Very active and friendly community.
– Start with fsharp.org
– F# Slack channel
– #fsharp onTwitter
5. A tour of F#
• Differences between C# and F#
– Concise syntax
– Type inference
– Different defaults
– Different philosophy
• Special to F#
– Functional-first
– Algebraic type system
– Interactivity
From least to most
important!
9. public class Person
{
public Person(string name, DateTime birthday)
{
_name = name;
_birthday = birthday;
}
private readonly string _name;
private readonly DateTime _birthday;
public string Name
{
get { return _name; }
}
public DateTime Birthday
{
get { return _birthday; }
}
}
Do we really need the
braces?
The indentation already gives
us all the clues we need.
10. public class Person
{
public Person(string name, DateTime birthday)
{
_name = name;
_birthday = birthday;
}
private readonly string _name;
private readonly DateTime _birthday;
public string Name
{
get { return _name; }
}
public DateTime Birthday
{
get { return _birthday; }
}
}
Example where braces
indicate blocks
11. public class Person =
public Person(string name, DateTime birthday) =
_name = name;
_birthday = birthday;
private readonly string _name;
private readonly DateTime _birthday;
public string Name =
get { return _name; }
public DateTime Birthday =
get { return _birthday; }
Example where indentation
indicates blocks
Is it really that much
harder to read?
12. public class Person =
public Person(string name, DateTime birthday) =
_name = name;
_birthday = birthday;
private readonly string _name;
private readonly DateTime _birthday;
public string Name =
get { return _name; }
public DateTime Birthday =
get { return _birthday; }
Use '=' to start blocks
13. public class Person =
public Person(string name, DateTime birthday) =
_name = name;
_birthday = birthday;
private readonly string _name;
private readonly DateTime _birthday;
public string Name =
get { return _name; }
public DateTime Birthday =
get { return _birthday; }
Shift up to use less
vertical space
14. People who
complain about
using a language
with syntactic
whitespace
People who
have spent time
using a language
with syntactic
whitespace
"Oddly enough, Python's use of whitespace stopped feeling unnatural
after about twenty minutes. I just indented code, pretty much as I would
have done in a C program anyway, and it worked."
- Eric Raymond
HelpfulVenn Diagram
Overlap
16. public class Person =
public Person(string name, DateTime birthday) =
_name = name;
_birthday = birthday;
private readonly string _name;
private readonly DateTime _birthday;
public string Name =
get { return _name; }
public DateTime Birthday =
get { return _birthday; }
A lot of duplication...
17. public class Person =
public Person(string name, DateTime birthday) =
...
public string Name =
get { return name; }
public DateTime Birthday =
get { return birthday; }
Use the constructor params directly
19. public class Person =
public Person(string name, DateTime birthday) =
...
public string Name =
get { return name; }
public DateTime Birthday =
get { return birthday; }
How often do you have more
than one constructor?
20. public class Person(string name, DateTime birthday) =
public string Name =
get { return name; }
public DateTime Birthday =
get { return birthday; }
22. public class Person(string name, DateTime birthday) =
public string Name =
get { return name; }
public DateTime Birthday =
get { return birthday; }
So why use ‘get’?
The class is immutable.
Every property is "get" only.
23. public class Person(string name, DateTime birthday) =
public string Name =
return name;
public DateTime Birthday =
return birthday; 'get' is gone!
24. public class Person(string name, DateTime birthday) =
public string Name =
return name;
public DateTime Birthday =
return birthday;
Who even likes typing semicolons anyway?
25. public class Person(string name, DateTime birthday) =
public string Name =
return name
public DateTime Birthday =
return birthday
Semicolons gone!
27. public class Person(string name, DateTime birthday) =
public string Name =
return name
public DateTime Birthday =
return birthday
28. public class Person(string name, DateTime birthday) =
public string Name =
name
public DateTime Birthday =
birthday
F# is an expression-
oriented language
33. class Person(string name, DateTime birthday) =
string Name =
name
DateTime Birthday =
birthday
Why do we have to
repeat the type?
Can't the compiler
figure it out for us?
35. class Person(string name, DateTime birthday) =
member this.Name =
name
member this.Birthday =
birthday
36. class Person(string name, DateTime birthday) =
member this.Name =
name
member this.Birthday =
birthday
member this.Age() =
var daysDiff = DateTime.Today.Subtract(birthday).Days
daysDiff / 365
38. class Person(string name, DateTime birthday) =
member this.Name =
name
member this.Birthday =
birthday
member this.Age() =
var daysDiff = DateTime.Today.Subtract(birthday).Days
daysDiff / 365
39. class Person(name: string, birthday: DateTime ) =
member this.Name =
name
member this.Birthday =
birthday
member this.Age() =
var daysDiff = DateTime.Today.Subtract(birthday).Days
daysDiff / 365
41. class Person(name: string, birthday: DateTime ) =
member this.Name =
name
member this.Birthday =
birthday
member this.Age() =
var daysDiff = DateTime.Today.Subtract(birthday).Days
daysDiff / 365
42. type Person(name: string, birthday: DateTime ) =
member this.Name =
name
member this.Birthday =
birthday
member this.Age() =
let daysDiff = DateTime.Today.Subtract(birthday).Days
daysDiff / 365
44. class Person(string name, DateTime birthday)
{
public string Name { get; } = name;
public DateTime Birthday { get; } = birthday;
public int Age() =>
DateTime.Today.Subtract(birthday).Days / 365;
}
Modern C# equivalent with auto-properties
and expression-bodied members
46. type Person = {
Name: string
Birthday: DateTime
}
let age person =
let daysDiff = DateTime.Today.Subtract(person.Birthday).Days
daysDiff / 365
47. // age : Person -> int
type Person = {
Name: string
Birthday: DateTime
}
let age person =
let daysDiff = DateTime.Today.Subtract(person.Birthday).Days
daysDiff / 365
48. Syntax is never the most important
thing about a programming language.
But…
51. public class Person
{
public Person(string name, DateTime birthday)
{
_name = name;
_birthday = birthday;
}
private readonly string _name;
private readonly DateTime _birthday;
/// <summary>
/// Full name
/// </summary>
public string Name
{
get { return _name; }
}
/// <summary>
/// Birthday
/// </summary>
public DateTime Birthday
{
get { return _birthday; }
}
}
public class Person(string name, DateTime birthday) =
/// Full name
public string Name =
get { return name; }
/// Birthday
public DateTime Birthday =
get { return birthday; }
C#
F#
You write
1/3 as much code.
55. Type inference
let doSomething f x =
let y = f (x + 1)
"hello" + y
Inferred type of doSomething :
f:(int -> string) -> x:int -> string
56. Type inference
// C# code
public IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
{
...
}
// F# code
let GroupBy source keySelector =
...
Benefits of type inference:
* Less typing
* Less noise, more logic
Here's a more complex example
58. F# has different defaults
• Immutable by default
– mutable is special case
• Non-null types/classes by default
– Nullable is special case
• Structural equality by default
– reference equality is special case
• Everything must be initialized
71. Different philosophies
• C# historically comes from C-like approach.
• F# comes from ML, a MetaLanguage for
proving things.
72. Goal: Predictable code
• Can you understand the code using only the
information that you have right in front of you?
• You’re not allowed to delve into other parts of
the codebase!
73. Example 1
The answer is
"hello world".
What value is y?
var x = 1;
DoSomething(x);
var y = "hello " + x;
75. Predictable code
C# is more predictable than JavaScript!
In C#, if you don't match up the types properly,
you get a compile-time error not a run-time error.
This is good!
76. How to make your language predictable:
• Variables should not be allowed to change their type
77. Example 2
// create two customers
var cust1 = new Customer(99, "J Smith");
var cust2 = new Customer(99, "J Smith");
// true or false?
cust1 == cust2;
78. How to make your language predictable:
• Variables should not be allowed to change their type
• Objects with the same values should be equal by
default.
79. Example 3
// create a customer and an order
var cust = new Customer(99, "J Smith");
var order = new Order(99, "J Smith");
// true or false?
cust.Equals(order);
80. How to make your language predictable:
• Variables should not be allowed to change their type
• Objects with the same values should be equal by
default.
• Comparing objects of different types is a compile-
time error.
81. Example 4
// create a customer
var cust = new Customer();
// what is the expected output?
Console.WriteLine(cust.Address.Country)
82. How to make your language predictable:
• Variables should not be allowed to change their type
• Objects with the same values should be equal by default.
• Comparing objects of different types is a compile-time error.
• Objects must always be initialized to a valid state. Not doing
so is a compile-time error.
83. Example 5
// create a customer
var cust = new Customer(99, "J Smith");
// add it to a set
var processedCustomers = new HashSet<Customer>();
processedCustomers.Add(cust);
// process it
ProcessCustomer(cust);
// Is the customer still in the set? True or false?
processedCustomers.Contains(cust);
You can't tell! Not predictable!
If ProcessCustomer mutates the customer, it
might change the hash
84. Example 5
// create a customer
var cust = new ImmutableCustomer(99, "J Smith");
// add it to a set
var processedCustomers = new HashSet<ImmutableCustomer>();
processedCustomers.Add(cust);
// process it and return the changes
var changedCustomer = ProcessCustomer(cust);
// Is the customer still in the set? True or false?
processedCustomers.Contains(cust);
Immutability forces
changed values to be
returned explictly. Original
value unchanged.
85. How to make your language predictable:
• Variables should not be allowed to change their type
• Objects with the same values should be equal by default.
• Comparing objects of different types is a compile-time error.
• Objects must always be initialized to a valid state. Not doing
so is a compile-time error.
• Once created, objects and collections must be immutable.
86. Example 6
// create a repository
var repo = new CustomerRepository();
// find a customer by id
var customer = repo.GetById(42);
// what is the expected output?
Console.WriteLine(customer.Id);
What happens if the customer is missing?
Is the customer null or what?
You can't tell! Not predictable!
87. Example 6
// create a repository
var repo = new CustomerRepository();
// find a customer by id
var customerOrError = repo.GetById(42);
// handle both cases
if (customerOrError.IsCustomer)
Console.WriteLine(customerOrError.Customer.Id);
if (customerOrError.IsError)
Console.WriteLine(customerOrError.ErrorMessage);
Instead, build error cases into the return type.
88. How to make your language predictable:
• Variables should not be allowed to change their type
• Objects with the same values should be equal by default.
• Comparing objects of different types is a compile-time error.
• Objects must always be initialized to a valid state. Not doing
so is a compile-time error.
• Once created, objects and collections must be immutable.
• Missing data or errors must be made explicit. No nulls
allowed.
89. F# aims to be a predictable language:
• Variables are not be allowed to change their type
• Objects with the same values are equal by default.
• Comparing objects of different types is a compile-time error.
• Objects must be initialized to a valid state. Not doing so is a
compile-time error..
• Once created, objects and collections are (generally)
immutable.
• Missing data or errors are (generally) made explicit. Nulls are
a code smell.
95. A function is a standalone thing,
not attached to a class
It can be used for inputs and outputs
of other functions
96. input
A function can be an output
A function is a standalone thing,
not attached to a class
It can be used for inputs and outputs
of other functions
97. output
input
A function can be an output
A function can be an input
A function is a standalone thing,
not attached to a class
It can be used for inputs and outputs
of other functions
98. output
input
input output
A function can be an output
A function can be an input
A function can be a parameter
A function is a standalone thing,
not attached to a class
It can be used for inputs and outputs
of other functions
99. output
input
input output
A function can be an output
A function can be an input
A function can be a parameter
A function is a standalone thing,
not attached to a class
It can be used for inputs and outputs
of other functions
From this simple foundation we can build complex systems
104. add1 double5 12
let add1 x = x + 1
let double x = x + x
let add1_double =
add1 >> double
let x = add1_double 5
Composition (F#)
105. add1 double square5 144
let add1_double_square =
add1 >> double >> square
let x = add1_double_square 5
Composition (F#)
106. Func<int, int> add1 = x => x + 1;
Func<int, int> doubl = x => x + x;
Func<int, int> square = x => x * x;
var composed =
add1.Compose(doubl).Compose(square);
composed(5);
Composition (C#)
113. Parameterize all the things
let printList() =
for i in [1..10] do
printfn "the number is %i" i
114. Parameterize all the things
let printList aList =
for i in aList do
printfn "the number is %i" i
115. Parameterize all the things
let printList anAction aList =
for i in aList do
anAction i
FPers would parameterize the action as well:
We've decoupled the
behavior from the data.
Any list, any action!
116. Parameterize all the things
public static int Product(int n)
{
int product = 1;
for (int i = 1; i <= n; i++)
{
product *= i;
}
return product;
}
public static int Sum(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
117. public static int Product(int n)
{
int product = 1;
for (int i = 1; i <= n; i++)
{
product *= i;
}
return product;
}
public static int Sum(int n)
{
int sum = 0;
for (int i = 1; i <= n; i++)
{
sum += i;
}
return sum;
}
Parameterize all the things
118. Parameterize all the things
let fold action initialValue list =
let mutable totalSoFar = initialValue
for item in list do
totalSoFar <- action totalSoFar item
totalSoFar
119. Parameterize all the things
let fold action initialValue list =
let mutable totalSoFar = initialValue
for item in list do
totalSoFar <- action totalSoFar item
totalSoFar
120. Parameterize all the things
let product n =
let initialValue = 1
let action productSoFar x = productSoFar * x
[1..n] |> List.fold action initialValue
let sum n =
let initialValue = 0
let action sumSoFar x = sumSoFar+x
[1..n] |> List.fold action initialValue
Lots of collection functions like this:
"fold", "map", "reduce", "collect", etc.
121. Big topic! Not enough time right now !
More at fsharpforfunandprofit.com/fppatterns
124. What is a type?
A type is a just a name
for a set of things
Set of
valid inputs
Set of
valid outputs
Function
125. Set of
valid inputs
Set of
valid outputs
Function
1
2
3
4
5
6
This is type
"integer"
A type is a just a name
for a set of things
126. Set of
valid inputs
Set of
valid outputs
Function
This is type
"string"
"abc"
"but"
"cobol"
"double"
"end"
"float"
A type is a just a name
for a set of things
127. Set of
valid inputs
Set of
valid outputs
Function
This is type
"Person"
Donna Roy
Javier Mendoza
Nathan Logan
Shawna Ingram
Abel Ortiz
Lena Robbins
GordonWood
A type is a just a name
for a set of things
128. Set of
valid inputs
Set of
valid outputs
Function
This is type
"Fruit"
A type is a just a name
for a set of things
129. Set of
valid inputs
Set of
valid outputs
Function
This is a type
containing functions
A type is a just a name
for a set of things
130. F# types can be composed
"Algebraic type system"
131.
132. New types are built from smaller types using:
“AND”
“OR”
133. Example: pairs, tuples, records
FruitSalad = One each of and and
“AND” types
type FruitSalad = {
Apple: AppleVariety
Banana: BananaVariety
Cherry: CherryVariety
}
134. Snack = or or
“OR” types
type Snack =
| Apple of AppleVariety
| Banana of BananaVariety
| Cherry of CherryVariety
136. Example of some requirements:
We accept three forms of payment:
Cash, Check, or Card.
For Cash we don't need any extra information
For Checks we need a check number
For Cards we need a card type and card number
137. interface IPaymentMethod
{..}
class Cash() : IPaymentMethod
{..}
class Check(int checkNo): IPaymentMethod
{..}
class Card(string cardType, string cardNo) : IPaymentMethod
{..}
In C# you would probably implement it as an
interface and a set of subclasses, like this:
138. type CheckNumber = int
type CardNumber = string
In F# you would probably implement by composing
types, like this
139. type CheckNumber = int
type CardNumber = string
type CardType = Visa | Mastercard
type CreditCardInfo = CardType * CardNumber
140. type CheckNumber = int
type CardNumber = string
type CardType = Visa | Mastercard
type CreditCardInfo = CardType * CardNumber
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CreditCardInfo
141. type CheckNumber = int
type CardNumber = string
type CardType = Visa | Mastercard
type CreditCardInfo = CardType * CardNumber
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
142. type CheckNumber = int
type CardNumber = string
type CardType = Visa | Mastercard
type CreditCardInfo = CardType * CardNumber
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
type Payment = {
Amount : PaymentAmount
Currency: Currency
Method: PaymentMethod }
144. type Deal = Deck –› (Deck * Card)
type PickupCard = (Hand * Card) –› Hand
Types are executable documentation
type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King | Ace
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = {Name:string; Hand:Hand}
type Game = {Deck:Deck; Players: Player list}
The domain on one screen!
145. Types are executable documentation
type CardType =Visa | Mastercard
type CardNumber = CardNumber of string
type CheckNumber = CheckNumber of int
type PaymentMethod =
| Cash
| Check of CheckNumber
| Card of CardType * CardNumber
146. Another big topic and not enough time
More on DDD and designing with types at
fsharpforfunandprofit.com/ddd