What is it, how to use it, how to use it to extend fastai.
from fastai2.vision.all import *

A very simple introduction

Let's say we want to create a function that does floor division // if the input is a int and normal division / if the input is a float.
One way of doing that is a function that checks (with ifs conditions) the input type and executes the appropriate operation.

def same_type_div(a, b):
  if type(a) is int:   return a//b
  if type(a) is float: return a/b
same_type_div(5, 2), same_type_div(5., 2)
(2, 2.5)

Alright, works well enough.
Let's say after some time we want to include complex numbers, let's say we want our users to give the complex numbers as strings and convert to complex internally. We now have to change the original function to the following:

def same_type_div(a, b):
  if type(a) is int:   return a//b
  if type(a) is float: return a/b
  if type(a) is str:   return complex(a)/complex(b)
same_type_div('1+1j', '1j')
(1-1j)

Simple enough right? But what happens when you can't modify the source code? (I mean, if the package is open source, you can always go to your local installation and modify the lines that you want, but good luck maintaining that updated. We should be able to modify the function behaviour without having to change the source code directly).

typedispatch comes to our rescue, let's say the library you're using handles int and float:

@typedispatch
def same_type_div2(a:int, b): return a//b
@typedispatch
def same_type_div2(a:float, b): return a/b

If you're used with standard Python you may be a bit confused with what just happened in the two cells above, we declared the same function twice! Your intuition must be saying "the latter is going to overwrite the former!".
Well, typedispatch will keep both versions for us, and when we call the function, it will dispatch the one that if finds more appropriate, let's check that:

same_type_div2(5, 2), same_type_div2(5., 2)
(2, 2.5)

Now, adding your custom implementation for complex numbers is simple as:

@typedispatch
def same_type_div2(a:str, b): return complex(a)/complex(b)
same_type_div2('1+1j', '1j')
(1-1j)

By using typedispatch we don't need to touch the source code!
We can be even more specific and define what happens depending on the type of b!
Let's create a function that return the modulus of the division if a is a str and b an int.

@typedispatch
def same_type_div2(a:str, b:int): return abs(complex(a)/b)
same_type_div2('1+1j', 2)
0.7071067811865476

And there is more, it also works with subclasses:

class MyInt(int): pass
same_type_div2('1+1j', MyInt(2))
0.7071067811865476

It correctly finds the implementation for int and uses that. But if we define a implementation for MyInt directly:

@typedispatch
def same_type_div2(a:str, b:MyInt): return complex(a)/b
same_type_div2('1+1j', MyInt(2))
(0.5+0.5j)

It starts using that! typedispatch will always try to use the "closest" implementation it can find for your type.