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!
Disclaimer: I do not claim credit for the code examples and much of the contents here, these are mostly extracts from the book by Chris Smith, Programming F#: A comprehensive guide for writing simple code to solve complex problems. In fact, if you’re thinking of learning F# and like what you read here, you should buy the book yourself, it’s easy to read and the author has gone go great lengths to keep things simple and included a lot of code examples for you to try out yourself.
You define functions the same way you define values, except everything after the name of the function servers as the function’s parameters. The following defines a function called square that takes an integer, x, and returns its square:
Unlike C#, F# has no return keyword. The last expression to be evaluated in the function determines the return type.
Also, from the FSI output above, it shows the function square has signature int -> int, which reads as “a function taking an integer and returning an integer”.
Take this add function for example:
Looking at this you might be wondering why does the compiler think that the add function only takes integers? The + operator also works on floats too!
The reason is type inference. Unlike C#, F# doesn’t require you to explicitly state the types of all the parameters to a function, it figures it out based on usage. Because the + operator works for many different types such as byte, int, and decimal, the compiler simply defaults to int if there is no additional information.
The following FSI snippet shows what type inference in action if we not only define the add function but also call it passing in floats, then the function’s signature will be inferred to be of type float -> float -> float instead:
However, you can provide a type annotation, or hint, to the F# compiler about what the types are. To do this, simply replace a function parameter with the following form ident -> (ident: type) like this:
This works because the only overload for + that takes a float as its first parameter is float -> float -> float, so the F# compiler infers y to be a float as well.
Type inference can reduce code clutter by having the compiler figure out what types to use, but the occasional type annotation is required and can sometimes improve code readability.
You can write functions that work for any type of a parameter, such as an identity function below:
Because the type inference system could not determine a fixed type for value x in the ident function, it was generic. If a parameter is generic, then that parameter can be of any type.
The type of a generic parameter can have the name of any valid identifier prefixed with an apostrophe, but typically letters of the alphabet starting with ‘a’ as you can see from the FSI snippet for the ident function above.
Writing generic code is important for maximizing code reuse.
Every value declared in F# has a specific scope, more formally referred to as a declaration space.
The default scope is module scope, meaning variables can be used anywhere after their declaration. However, values defined within a function are scoped only to that function.
The scoping of a variable is important because F# supports nested functions – i.e. you can declare new function values within the body of a function. Nested functions have access to any value declared in a higher scope as well as any new values declared within itself. The following examples shows this in action:
In F#, having two values with the same name doesn’t lead to a compiler error; rather it simply leads to shadowing. When this happens, both values exists in memory, except there is no way to access the previously declared value. For example:
This technique of intentionally shadowing values is useful for giving the illusion of updating values without relying on mutation. Think strings in C#, which is an immutable type that allows reassignment using the same shadowing technique.
You can branch control flow using the if keyword which works exactly like an if statement in C#:
F# supports if-then-else structure, but the thing the sets if statements in F# apart is that if expressions return a value:
F# has some syntactic sugar to help you combat deeply nested if expression with the elif keyword:
Because the result of the if expression is a value, every clause of an if expression must return the same type.
But if you only have a single if and no corresponding else, then the clause must return unit, which is a special type in F# that means essentially “no value”.
Besides the primitive types, the F# library includes several core types that will allow you to organize, manipulate and process data:
The unit type is a value signifying nothing of consequence. unit can be thought of as a concrete representation of void and is represented in code via ():
if expressions without a matching else must return unit because if they did return a value, what would happen if else was hit?
Also, in F#, every function must return a value, think method in C# and the void return type, so even if the function doesn’t conceptually return anything then it should return a unit value.
The ignore function can swallow a function’s return value if you want to return unit:
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