Fastcore migration guide

Moving from fastcore to fasttransform

Fastai users

If you’re using fastai, there’s nothing you need to do - these changes will be included in future fastai releases and your existing code will continue to work as before.

Fastcore dispatch users

Fastcore’s type dispatch system is being replaced with Plum, a more robust multiple dispatch library. This section covers how to update your code that uses @typedispatch or TypeDispatch.

Install plum:

pip install plum-dispatch

Simple decorator migration

For most usecases switching to plum is as easy as switching out fastcore’s typedispatch for plum’s dispatch.

Before:

from fastcore.dispatch import typedispatch

@typedispatch 
def proc_fc(x: int): return x + 1

@typedispatch
def proc_fc(x: float): return x * 2

After:

from plum import dispatch

@dispatch 
def proc_pl(x: int): return x + 1

@dispatch
def proc_pl(x: float): return x * 2

Converting TypeDispatch to Plum Function

If you’re using TypeDispatch directly, you’ll need to use Plum’s Function class instead.

The key differences are:

  1. Initialization uses Function() instead of TypeDispatch()
  2. Methods are added using .register() or .dispatch()
  3. Function inspection uses .methods instead of print()

Initialization

# setup
import numbers

def f2(x:int, y:float)->float: return x+y               #int and float for 2nd arg
def f_nin(x:numbers.Integral)->int: return x+1          #integral numeric
def f_ni2(x:int): return x                              #integer
def f_fl(x:float)->float: return x                      #float
def f_bll(x:bool|list)->bool|list: return x             #bool or list
def f_num(x:numbers.Number)->numbers.Number: return x   #Number (root of numerics)

fs = [
    f2, 
    f_nin, 
    f_ni2, 
    f_fl,
    f_bll, 
    f_num
]

Before:

from fastcore.dispatch import TypeDispatch
t_fc = TypeDispatch(fs)
t_fc
(bool,object) -> f_bll
(int,float) -> f2
(int,object) -> f_ni2
(Integral,object) -> f_nin
(float,object) -> f_fl
(list,object) -> f_bll
(Number,object) -> f_num

After:

from plum import Function

t_pl = Function(f2)
for f in fs: t_pl.register(f)
len(t_pl.methods)
6

Adding new functions to an initialized TypeDispatch/Function

Before:

t_fc.add(lambda x: x**2)
t_fc
(bool,object) -> f_bll
(int,float) -> f2
(int,object) -> f_ni2
(Integral,object) -> f_nin
(float,object) -> f_fl
(list,object) -> f_bll
(Number,object) -> f_num
(object,object) -> <lambda>

After:

t_pl.register(lambda x: x**2)  # or t_pl.dispatch(lambda x: x**2)
t_pl.methods

List of 7 method(s):
    [0] f2(x: int, y: float) -> float                                                                              
        <function f2 at 0x10be6df80> @                                                                             
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:4                                
    [1] f2(x: numbers.Integral) -> int                                                                             
        <function f_nin at 0x10be6dee0> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:5                                
    [2] f2(x: int)                                                                                                 
        <function f_ni2 at 0x10be6e020> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:6                                
    [3] f2(x: float) -> float                                                                                      
        <function f_fl at 0x10be6e0c0> @                                                                           
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:7                                
    [4] f2(x: bool | list) -> bool | list                                                                          
        <function f_bll at 0x10be6e160> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:8                                
    [5] f2(x: numbers.Number) -> numbers.Number                                                                    
        <function f_num at 0x10be6e200> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:9                                
    [6] f2(x: Any)                                                                                                 
        <function <lambda> at 0x10be85260> @                                                                       
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/2634702974.py:1                               

Combining multiple instances of TypeDispatch/Function

Fastcore provided a way to initialize TypeDispatch with pre-existing TypeDispatch objects as its base(s).

Before:

def f_str(x:str): return x+'1'

t_fc2 = TypeDispatch(f_str, bases=t_fc)
t_fc2
(str,object) -> f_str
(bool,object) -> f_bll
(int,float) -> f2
(int,object) -> f_ni2
(Integral,object) -> f_nin
(float,object) -> f_fl
(list,object) -> f_bll
(Number,object) -> f_num
(object,object) -> <lambda>

Plum does not provide this feature directly. But we’ve written a function to help you accomplish a similar result.

After:

from fasttransform.transform import _merge_funcs

t_pl_new = Function(f_str).dispatch(f_str)
t_pl2 = _merge_funcs(t_pl_new, t_pl)
t_pl2.methods

List of 8 method(s):
    [0] f_str(x: int, y: float) -> float                                                                           
        <function f2 at 0x10be6df80> @                                                                             
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:4                                
    [1] f_str(x: numbers.Integral) -> int                                                                          
        <function f_nin at 0x10be6dee0> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:5                                
    [2] f_str(x: int)                                                                                              
        <function f_ni2 at 0x10be6e020> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:6                                
    [3] f_str(x: float) -> float                                                                                   
        <function f_fl at 0x10be6e0c0> @                                                                           
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:7                                
    [4] f_str(x: bool | list) -> bool | list                                                                       
        <function f_bll at 0x10be6e160> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:8                                
    [5] f_str(x: numbers.Number) -> numbers.Number                                                                 
        <function f_num at 0x10be6e200> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/891647847.py:9                                
    [6] f_str(x: Any)                                                                                              
        <function <lambda> at 0x10be85260> @                                                                       
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/2634702974.py:1                               
    [7] f_str(x: str)                                                                                              
        <function f_str at 0x10be85ee0> @                                                                          
    /var/folders/0h/5ktbjhg17ns6j8zgdsgvzvy80000gn/T/ipykernel_14825/3062264994.py:1                               

Obtain the raw function and return type for given arg types

If you wanted to retrieve the raw function that matches given runtime input types. Then with TypeDispatch you could use the __getitem__ method.

Before:

t_fc[int]
<function __main__.f_ni2(x: int)>

If you wanted to retrieve that raw function’s return type then you can use the .returns method.

Before:

t_fc.returns(5)

Plum’s Function does not provided the exact same functionality. But you can pass arguments to .resolve_method or ._resolve_method_with_cache method to find the matching raw function and it’s return type.

After:

raw_func, ret_type = t_pl.resolve_method((5,))
print(raw_func)
print(ret_type)
<function f_ni2>
typing.Any

or the cached version:

raw_func, ret_type = t_pl._resolve_method_with_cache((5,))
print(raw_func)
print(ret_type)
<function f_ni2>
typing.Any

Breaking Change: Ambiguous Type Matching

A key behavioral difference in Plum is how it handles ambiguous type matches:

  • Fastcore: Silently uses the last defined function when multiple matches exist
  • Plum: Raises an AmbiguousLookupError to prevent unexpected behavior

Example:

@typedispatch
def f2_fc(x:int|float): return x+2
@typedispatch
def f2_fc(x:int|str): return x*3

f2_fc(5)
15

While plum will raise an AmbiguousLookupError.

from plum import AmbiguousLookupError

@dispatch
def f2_pl(x:int|float): return x+2
@dispatch
def f2_pl(x:int|str): return x*3

try: f2_pl(5)
except AmbiguousLookupError: print("Caught expected AmbiguousLookupError")
Caught expected AmbiguousLookupError

Moving from fastcore to fasttransform

If you’re using Transform or Pipeline, follow these steps:

install fasttransform

  1. Install the new package:

    pip install fasttransform

Update imports

# Before
from fastcore.transform import Transform, Pipeline
# After
from fasttransform import Transform, Pipeline

Initializing Transform

In fasttransform, you can now define multiple encode/decode methods either through subclassing (like before) or directly:

# Before (subclassing required)
from fastcore.transform import Transform as FCTransform

class MyTransform(FCTransform):
    def encodes(self, x:int): return "enc int!"
    def encodes(self, x:str): return "enc str!"
    
    def decodes(self, x:int): return "dec int!"
    def decodes(self, x:str): return "dec str!"

fct = MyTransform()
fct
MyTransform:
encodes: (int,object) -> encodes
(str,object) -> encodes
decodes: (int,object) -> decodes
(str,object) -> decodes

While the old behavior is still supported, you can now also do the following:

# After (direct initialization possible)
from fasttransform import Transform

def my_transform(x:int): return "enc int!"
def my_transform2(x:str): return "enc str!"
def my_transform_dec(x:int): return "dec int!"
def my_transform_dec2(x:str): return "dec str!"

Transform(enc=(my_transform,my_transform2), dec=(my_transform_dec,my_transform_dec2))
my_transform(enc:2,dec:2)

Advanced: Custom Transform Implementation

If you’ve overridden internal methods like _call or _do_call in your custom Transform classes:

  1. Review the new Plum-based implementation in the source code
  2. Update your methods to work with Plum’s Function class instead of TypeDispatch
  3. If you need help, create an issue