Skip to content

Instantly share code, notes, and snippets.

@ringvold
Last active December 12, 2019 22:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ringvold/830d3de1b3e0fa32398a789ac9308a6c to your computer and use it in GitHub Desktop.
Save ringvold/830d3de1b3e0fa32398a789ac9308a6c to your computer and use it in GitHub Desktop.
Functors - What are they?

Functors - What are they?

In this article we will be looking at a concept in FP called functor.

A functor might sound very strange and esoteric but chances are you have used it in some ways. Some of you probably a lot! In this article we will look at what they are and some reasons they are usefull.

Lets start with a definition of a functor and then try to make sense of it. This will not be a comprehensive explanation and we will be glossing over some details for in the interest of brevity but it will hopefully be enough you get you started.

"A functor is a structure with one (or more) values in it that has a function that can transform the value".

This might seem to make little sense now but stay with me. We will start in off in a place many might be familiar: JavaScript!

NB: This is not an official definition but it gets us stared in understanding it.

My first functor

The functor that are used the most without people even knowing it are probably arrays in JavaScript. One of the ways that you know something is a functor is if it has the map function.

The map function on arrays are used to transform (or map 😎) the values inside it to another type (or another instance of the same type):

[1,2,3].map(x => String(x))
// or just:
[1,2,3].map(String)
// outputs: ["1","2","3"]

Map takes one parameter which is a one-parameter function. When sendt (or applied) to map this function will in turn be called with each of the values in the array giving us a new array containing the new transformed values. In the above example we are transforming the numbers to strings.

(Since we know the parameter to map is a function that takes one parameter and the String function is a one-parameter function we can just pass in the function directly without a wrapping anonymous function.)

Note that when mapping arrays we always get back an array. The values in it can be transformed into strings, ints, objects or arrays (or any valid JS type) but they will always be inside an Array.

The Maybe type

In the statically typed functional language Elm these functional concepts are used all the time but without actually talking about the technical names. Lets look at one of the most used that is not a list/array: Maybe.

Elm does not have null or undefined so values that might not be available have to be represented in other ways.

For this the Elm standard library has a type called Maybe and is defined like this:

type Maybe a
    = Just a
    | Nothing

This defines a type called Maybe which contains a value a (small letter means any type). It represents to different "states": Just for when you have a value and Nothing when you have nothing. ;)

Lets say your system has a profile page where the user reports their age. This can be modelled several ways but we choose to model it as Maybe Int so its explicit when the user have not supplied the value yet.

(

user = { name = "John Doe", age = Just 42 }

)

Unfortunately we can only get the age as a string from the input field so we will end up with a value of Maybe String instead. This does not match our type Maybe Int. But fear not! Map to the rescue! With [toInt][Sting.toInt] and [map][Maybe.map] this is easily fixed. ^^,

ageToInt : Maybe String -> Maybe Int
ageToInt maybeString =
    Maybe.map String.toInt maybeString

This function takes a paramter maybeString of type Maybe String and returns Maybe Int. Here are some usages and its resulting output:

ageToInt (Just "42")
-- output: Just 42


ageToInt Nothing
-- output: Nothing


ageToInt (Just "not a number")
-- output: Nothing

In the last example we get Nothing back as String.toInt fails (it does not know how to convert "not a number" to an Int) and returns Nothing which in turn Maybe.map returns us. To use this converted value we can use a case expression:

isOver18 maybeString =
    case ageToInt maybeString of
        Just age ->
            age > 18
        
        Nothing ->
            False

Other functors

What are being mapped are up to the different types or structures in question. The Maybe type is fairly intuitive in that we understand that when we have and instance of Nothing there is nothing to do and it is relatively simple to just use a case expression to access and transform the value that way. If the type/structure we are working on is more complicated like the RemoteData type the map function makes our life easier.

type RemoteData e a
    = NotAsked
    | Loading
    | Failure e
    | Success a

This type represents and helps us model the states we can have related to an HTTP request. In may situations the interesting part is the Success state and its value. RemoteData.map makes it easy for us to transform this value without a case statement where we need to handle every possible state of the type.

It is important to note that it is the structure/type that decides what values is given access to in a map. For lists/array it is all the values in it one by one, for Maybe it is the Just case, and for RemoteData it the Success case when we know we have git a value from the server. For other structures there are other rules, but often it helps do something to a value only if the right conditions are met without exhausting checking, that is done by the structure/type itself.

There is a lot more to be said about functors but I hope this can get you started in understanding and exploring them more. :)

@bendiksolheim
Copy link

Fin og lettlest artikkel! Noe språkfusk som jeg har kommentert under, men jeg savner også litt motivasjon rundt hvorfor man trenger functorer. Kanskje du kunne utvidet den første seksjonen med noen av fordelene man får fra functorer, og så la resten av artikkelen være eksempler på bruken?

  • looking at a concept in FP called functor. -> tror du kan stryke "in FP" her, setningen står like bra uten.
  • Lets start with a definition of a functor and then try to make sense of it -> Lets start with a definition, and then try to make sense of it.
  • glossing over some details for in the interest -> glossing over some details in the interest
  • "A functor is a structure with one (or more) -> Kanskje du kan sette denne i en blockquote?

    A functor is a structure with one (or more)

  • We will start in off in a place many might be familiar -> We will start in off in a language many might be familiar with
  • When sendt (or applied) -> When sent (or applied)
  • containing the new transformed -> containing the newly transformed
  • most used that is not a list/array -> most used besides lists/arrays
  • called Maybe, which is defined like this:
  • where the user reports their age -> where the user can report their age
  • so its explicit when the user have not supplied the value yet -> so it’s explicit when no age is entered
  • Litt usikker på hva parentesene rundt eksempelet user = { ... gjør?
  • With [toInt][Sting.toInt] and [map][Maybe.map] -> Mangler det noe her? Skal det muligens være linker til Elm-docen? [toInt][Sting.toInt] mangler også en r i String :)
  • This function takes a paramter -> This function takes a parameter
  • What are being mapped are up to the -> What is being mapped is up to the
  • states we can have related to an HTTP request. -> states a HTTP request can be in.
  • In may situations the -> In many situations the
  • we have git a value from the server. -> we have received a value from the server.
  • but often it helps do something -> but often it helps us do something
  • without exhausting checking, -> without exhaustive checking,

@morteako
Copy link

Fin artikkel som kan være en god intro for folk som ikke har vært borti functors før.
Men det er noen upresisheter og spesielt feil som bør rettes,

"A functor is a structure with one (or more) values in it that has a function that can transform the value".
Jeg skjønner ikke helt den definisjonen.
Hva med "A functor is a structure that has a mapping function that can transform the values inside the functor"?

Tror det har blitt noe feil i ageToInt-eksempelet

ageToInt : Maybe String -> Maybe Int
    =  Maybe.map String.toInt maybeString

Den typen stemmer ikke helt
Siden String.toInt har typen String -> Maybe Int,
så blir det vel `ageToInt : Maybe String -> Maybe (Maybe Int)
Og det fører også til at eksemplene er feil

Det man vil ha her er vel monadisk oppførsel med Maybe.andThen, men tror det er utenfor blogposten.
Så burde kanskje helle bare bytte ut String.toInt med en funksjon som ikke gir Maybe?

Jeg vil kanskje også droppet
å ha 2 versjoner av list.map(String), da jeg tror det bare fjerner fokus fra Functor/map til funksjoner/konstruktører i javascript.

@ringvold
Copy link
Author

Veldig gode tilbakemeldinger fra begge! 👍 Ser jeg må jobbe med eksempelet her ja.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment