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[<em>]
. 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 [</em>]
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
orlevel zero
type in Scala is, akaString
,Int
,Float
, etc. - A
first order
orlevel one
type in Scala is, akaList
,Option
,Map
,Either
. - A type constructor(
[_]
) is, aka a function that takes a type and returns a typeString => String
we can finally look at what the F[<em>]
is in Scala. The F[</em>]
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.