You can become a serverless blackbelt. Enrol to my 4-week online workshop Production-Ready Serverless and gain hands-on experience building something from scratch using serverless technologies. At the end of the workshop, you should have a broader view of the challenges you will face as your serverless architecture matures and expands. You should also have a firm grasp on when serverless is a good fit for your system as well as common pitfalls you need to avoid. Sign up now and get 15% discount with the code yanprs15!
In F#, if-else if-else control flow is expressed in the form if Exp then Exp elif Exp then Exp else Exp:
let f n =
if n = 42 then
printfn "%d is the answer to the ultimate question of life, the universe and everything" n
elif n % 2 = 0 then
printfn "%d is even" n
printfn "%d is odd" n
In Erlang, there’s not explicit else if or else clauses, instead you just specify multiple clauses and the last catch-all (true –> …) is Erlang’s equivalent of the ‘else’ clause.
f (N) –>
if N =:= 42 -> io:format("~p is the answer to the ultimate question of life, the universe and everything", [N]);
N rem 2 =:= 0 -> io:format("~p is even", [N]);
true -> io:format("~p is odd", [N]) % this is Erlang’s equivalent to an ‘else’
When you’re writing Erlang you’re usually discouraged from using ‘else’ or ‘true’ branches altogether and should instead replace them with if clauses that cover all logical branches to help avoid masking otherwise hard-to-detect bugs.
The preferred way (for me at least) to do conditional branching in F# is to use the match-with expression rather than using ifs. The logic expressed in the function f from the above example can easily be expressed in a match-with instead:
let g n =
match n with
| 42 -> printfn "%d is the answer to the ultimate question of life, the universe and everything" n
| _ when n % 2 = 0 -> printfn "%d is even" n
| _ -> printfn "%d is odd" n
Erlang’s counterpart to match-with is the case-of expression, which looks a lot like match-with:
g (N) –>
case N of
42 -> io:format("~p is the answer to the ultimate question of life, the universe and everything", [N]);
_ when N rem 2 =:= 0 -> io:format("~p is even", [N]);
_ -> io:format("~p is odd", [N])
In F#, you can cast a value of type T into a value of type V like this:
let n = int “42”
provided that there exists an explicit cast operator that lets you convert a value of type T to type V.
In Erlang, type conversion is achieved with the help of built-in functions that are generally named T_to_V where T and V are the names of the source and destination types:
N = list_to_integer(“42”).
There are many other such built-in functions, including: atom_to_binary, atom_to_list, binary_to_atom, list_to_atom, integer_to_list, …
From the above example, you might be wondering why the built-in function that converts a string to an integer is called list_to_integer as opposed to string_to_integer. This is because there is no real string type in Erlang!
In Erlang, strings are lists of integers whose elements are all integers that represent printable characters:
1> [52, 50].
2> % 2 is not a printable character, so this is treated as an integer list
2> [52, 50, 2].
[52, 50, 2]
3> [ 104, 101, 108, 108, 111 ].
4> % you can use the $ operator to find out the integer value of a character
I’m sure many a developer has enjoyed a wonderful WTF moment when they realised the lack of a native string type in Erlang, but given its origin in the telecom world where string manipulation is seldom required it’s perhaps not surprising that the language designers never saw fit to make string manipulation more natural.
But seriously, WTF!
Type testing is performed with the : ? operator in F#:
let f (n : obj) =
match n with
| : ? int as nInt -> printfn "%d is an integer" nInt
| : ? float as nFloat -> printfn "%f is a float" nFloat
| : ? string as nStr -> printfn "%s is a string" nStr
In Erlang, you can use a number of built-in functions named is_T where T is the name of the type:
f (N) –>
case N of
_ when is_integer(N) -> io:format("~p is an integer", [N]);
_ when is_float(N) -> io:format("~p is a float", [N]);
_ when is_list(N) -> io:format("~p is a list", [N])
A recursive function in F# needs to be decorated with the rec keyword, a factorial function in F# would look like this:
let rec fac n =
match n with
| 0 –> 1
| _ -> n * fac(n – 1)
In Erlang, you can specify multiple overloads (called function clauses) for the same function in a style similar to facts in Prolog, recursion is a simple matter of having one or more of these clauses call itself:
fac (N) when N =:= 0 -> 1;
fac (N) when N > 0 -> N * fac(N – 1).
You might notice that neither implementations above are tail recursive. To make tail recursive versions of the above examples, simply add an accumulator to the function, the F# version will probably look something along the lines of:
let fac n =
let rec tailFac n acc =
match n with
| 0 –> acc
| _ -> tailFac (n – 1) (n * acc)
tailFac n 1
Notice how I implemented the tail recursive function as an inner function to fac so that the consumers of fac have a natural and easy to use API and don’t have to be aware that the accumulator needs to be initialized with 1 for it to function correctly (which is an implementation detail that one should not depend upon).
This type of encapsulation is easy to achieve in Erlang and the above will translate nicely into:
-export([fac/1]). % only expose the fac (N) function to consumers of this module
fac (N) -> tail_fac(N, 1).
%% these functions are private to the module
tail_fac (0, Acc) -> Acc;
tail_fac (N, Acc) when N > 0 -> tail_fac(N – 1, N * Acc).
Higher Order Functions
In F#, the List module defines a number of high-order functions that lets you perform map, filter, zip, etc. operations against a list, e.g. to double every element in a list you can write:
let doubles = List.map (fun x -> x * 2) [1..10]
// you can also write the above as the following
let doubles2 = List.map ((*) 2) [1..10]
This code will look something along the line of:
Doubles = lists:map(fun (X) -> X * 2 end, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).
With Erlang’s anonymous functions you can also specify multiple function clauses like you can with a normal function and make use of when guards:
NewList = lists:map(fun (1) –> 10;
(X) when X rem 2 =:= 0 -> X * 2;
(X) -> X * 3 end,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).
Hi, I’m Yan. I’m an AWS Serverless Hero and I help companies go faster for less by adopting serverless technologies successfully.
Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.
Skill up your serverless game with this hands-on workshop.
My 4-week Production-Ready Serverless online workshop is back!
This course takes you through building a production-ready serverless web application from testing, deployment, security, all the way through to observability. The motivation for this course is to give you hands-on experience building something with serverless technologies while giving you a broader view of the challenges you will face as the architecture matures and expands.
We will start at the basics and give you a firm introduction to Lambda and all the relevant concepts and service features (including the latest announcements in 2020). And then gradually ramping up and cover a wide array of topics such as API security, testing strategies, CI/CD, secret management, and operational best practices for monitoring and troubleshooting.
If you enrol now you can also get 15% OFF with the promo code “yanprs15”.
Check out my new podcast Real-World Serverless where I talk with engineers who are building amazing things with serverless technologies and discuss the real-world use cases and challenges they face. If you’re interested in what people are actually doing with serverless and what it’s really like to be working with serverless day-to-day, then this is the podcast for you.
Check out my new course, Learn you some Lambda best practice for great good! In this course, you will learn best practices for working with AWS Lambda in terms of performance, cost, security, scalability, resilience and observability. We will also cover latest features from re:Invent 2019 such as Provisioned Concurrency and Lambda Destinations. Enrol now and start learning!
Check out my video course, Complete Guide to AWS Step Functions. In this course, we’ll cover everything you need to know to use AWS Step Functions service effectively. There is something for everyone from beginners to more advanced users looking for design patterns and best practices. Enrol now and start learning!
Here is a complete list of all my posts on serverless and AWS Lambda. In the meantime, here are a few of my most popular blog posts.
- All you need to know about caching for serverless applications
- Lambda optimization tip – enable HTTP keep-alive
- You are wrong about serverless and vendor lock-in
- You are thinking about serverless costs all wrong
- Just how expensive is the full AWS SDK?
- Check-list for going live with API Gateway and Lambda
- How to choose the right API Gateway auth method
- CloudFormation protip: use !Sub instead of !Join
- AWS Lambda – should you have few monolithic functions or many single-purposed functions?
- Guys, we’re doing pagination wrong
- Top 10 Serverless framework best practices
- How to break the “senior engineer” career ceiling
- My advice to junior developers