TheSharperDev

Educating about C# and F#

Introduction to Functions - F#

Functions are an important piece of any programming language, including F#. Lets dive in.

F# is Functional

F# is a functional language. Here’s a simple F# function.

let add1 x y = x + y

Like really simple. It just adds two numbers.

You could also add parenthesis:

let add2 (x, y) = x + y

Or types to this function.

let add3 (x: double, y: double): double = x + y

F# Infers Types

It might surprise you that in F# types are optional. In most languages, types are usually required or prohibited.

F# is a typed language, but types aren’t required because the complier can infer types.

Lets revisit all our functions, and provide the types that F# inferred (except for the last case because we provided them ourselves):

//int -> int -> int
let add1 x y = x + y

//int * int -> int
let add2 (x, y) = x + y

//double * double -> double
let add3 (x: double, y: double): double = x + y

Lets explore add2 first. This probably what you’re used to when it comes to functions. Two ints are required, then once two ints are provided, an int is returned.

add3 is similar to add2, except we didn’t like the types F# inferred. So we provided our own.

F# can generally infer types, but you’re able to specify your own as required.

add1 is unique/different because it’s leveraging partial application. A hallmark feature of functional languages.

Partial Application

Lets start with an example. We can take add1, and do this to it:

let add1 x y = x + y
let add_pa = add1 3
let result = add_pa 3 //result = 6

Even though add1 takes two parameters, we were able to call it with one.

Which created another function add_pa (add partial application), which we were able to call again to sum our numbers (3 + 3 = 6).

Lets take a look at those types signatures.

//int -> int -> int
let add1 x y = x + y

//int -> int
let add_pa = add1 3

//int
let result = add_pa 3

Everytime we provide an int, the type signatures loses an int.

Partial application allows us to provide some of the inputs, then get a new function back with the rest of the inputs.

Translating our type signatures:

int * int -> int - Takes two ints and returns a int.

int -> int -> int - Takes an int, that returns an intermediate function that takes an int, which returns an int.

int -> int * int -> int - Takes an int, that returns a function that takes two ints, which returns an int.

int -> string -> int -> string - Takes an int, that returns a function that takes a string, which returns a function that takes an int, which returns a string.

Partial application gives functions a lot of flexibility.

This might be a new idea, so lets introduce another new idea. What’s the difference between a function and a method?

Function v Method

If functional programming is new to you, you might use function and method pretty interchangeably. I know I do that a bit.

Generally speaking, here are the technical definitions (use as you desire):

  • Method - attached to a class or object
  • Function - independent of a class or object

In C#, if you wanted a similar add method, you’d have to declare it inside a class.

public static class Mathy
{
    public static int add(int x, int y) => x + y;
}

In F#, functions are inside a module, but aren’t attached to a class. I can have functions all day but no classes.

This fact enables functions that are dataless.

Functions Are Dataless

Should you prefer functions over methods?

To answer that, lets discuss data and behavior.

Here’s a C# example of data and behavior that are combined.

public class Human
{
    public int Age { get; set; }
    public bool IsAdult => Age >= 18;
}

The data (Age) exists in the same class as the behavior (IsAdult). One cannot exist without the other.

Here is a F# example of those concepts broken apart.

type Human = {
    Age: int
}
let IsAdult h = h.Age >= 18

Our data (Age) exists separately from the behavior (IsAdult).

A class and method allows data and behavior to coexist.

But functions prevent data and behavior from coexisting.

In practice, I find that separating data from behavior leads to better organized code.

Because F# functions are dataless, when reading through a method you’re not wondering if they’re modifying class data, or something passed it.

In languages like C#, you’re also able to separate data and behavior. But it’s on you the programmer to ensure that happens. Versus the langauge supporting/guiding/forcing you to that code organization.

Use Functions, Rule the World

That’s a brief overview of functions. Some main points about them and why they’re useful.

Do you think functions are good? Lets start a function fanclub! 🤪