What is F[_] in Scala?

What is F[_] in Scala?

Scala’s type system is one of the most sophisticated type systems. For people trying to learn this beautiful language, understanding what these types are can be complicated.

One of these types is the F[_]. I remember being confused and asking myself what it was and why I even needed it. Adding to this confusion was the different names given to the [_] in F[_] in the Scala community. Some engineers call it wildcard, one slot, or hole, while others call it a joker box.

    trait Traverse[F[_]] {
        def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]]
    }

Confusing right? Don’t worry!

In this blog, I will try my best to explain what it is and why you even need it.

Before we can understand what F[_] is, we first need to understand types in Scala.

Scala has proper types, or as I would call them, level zero types, such as Int, Float, Double, and String. Level zero types or proper types are types that can be attached to a value by themselves. This is why it is possible to say:

val company : String = "47 Degrees"

val magicNumber: Int = 47

What about List, Option, Either, and Map? What are they? Well, these are what we call first-order types or level one types because they can’t be attached to a value by themselves. If we gave the expression below to the Scala compiler, we would get back an error message:

val countries: List = List("🇬🇭 ", "🇪🇸 ", "🇺🇸", "🇬🇧", "🇯🇵" )

1 |val countries: List = List("🇬🇭 ", "🇪🇸 ", "🇺🇸", "🇬🇧", "🇯🇵" )
  |               ^^^^
  |               Missing type parameter for List

This is because the language won’t allow us to use List as a type. It wants us to say a list of something List[_].

In order for this to compile, we need to pass the List a level zero type or a proper type. For this reason, the example above would be:

val countries: List[String] = List("🇬🇭 ", "🇪🇸 ", "🇺🇸", "🇬🇧", "🇯🇵" )

This means that first-order types or level one types like List, Option, Map, and Either are generic types with a type constructor [_] that takes a proper type or level zero type, Int, String, Float, etc., to produce other level zero types.

List // This is a level one type

and

List[Int] // This is a level zero type

We made mentions of two new words, Type Constructor and Generic Types, that we need to understand.

What is a generic type?

Assuming we had a class called MyStack

class MyStack[A] {

    //some code here
}

We say that MyStack is generic because, whatever code we write inside of the MyStack class, inside the {}, will work for any type A.

You can think of class MyStack[A] as a template to define many classes at the same time: when you write MyStack[Double], or MyStack[Int], you get a copy of MyStack in which every A has become a Double, or an Int, respectively. This is what we mean by a type being generic in this instance.

What is a type constructor [_] or a higher kinded type?

A type constructor is something like a function that takes a type as an argument and returns a type.

(Int) => Int  

or

String => String

So a type constructor List[_] is just a function of type

(A) => List[A]

or

String => List[String]

Now that we have an understanding of what

  • A proper or level zero type in Scala is, aka String, Int, Float, etc.
  • A first order or level one type in Scala is, aka List, Option, Map, Either.
  • A type constructor([_]) is, aka a function that takes a type and returns a type String => String

we can finally look at what the F[_] is in Scala. The F[_] simply means that F is a type parameter, which is itself a type constructor or a type with a type parameter. As we stated earlier, such a type can be a List,Option, or even a Scala Future. Using F[_] is a way to abstract over level-one types, so that they all can share common functionalities in order for us not to repeat these same functionalities for List, Option, Future, etc.

Life would be pretty boring if we had to do repeat ourselves.

Let’s say we have a My47Degrees class that takes a level one type. We could write something like this:

class My47Degrees[F[_]] {
    def map[A, B](fa: F[A])(f:A => B): F[B]
}

What this means is that we could replace F[_] with any level one type and have it use a common method called map so we could say:

new My47Degrees[List] or new My47Degrees[Option]

Without the F[_], we would have done something like:

class My47DegreesForList {
    def map[A, B](fa: List[A])(f:A => B): List[B]
}

class My47DegreesForOption {
    def map[A, B](fa: Option[A])(f:A => B): Option[B]
}

Isn’t this nice? We have just abstracted over all first-order types with a similar function called map. And, for this, we need to use the F[_] parameter. That is it.

Think about the benefits this will provide, and let us know if you have any questions.

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.