It is possible that your $A$ is the Jacobian of a residual vector function $f$ which you need to drive to zero to solve your problem. It can also be the Jacobian corresponding to space or space-time discretisation of a PDE.
For instance for the 1D heat transfer $\partial_t T = D \partial_{xx} T$ on a uniform grid, applying a finite difference scheme in space could be, for the $i$-th space point:
$$\partial_t T_i = \dfrac{D}{\Delta x^2} (T_{i+1} -2 T_{i} + T_{i-1})$$
In matrix form, you get:
$$\partial_t \mathbf{T} = \dfrac{D}{\Delta x^2}L \mathbf{T} = A \mathbf{T}$$
With $L$ the classical Laplacian matrix.
Usually, for more complex problems, you do not form $A$, sometimes you may not even know how it's formed. Instead you write a function which directly computes the time derivatives at each point as per the first discrete equation.
Computing $A\mathbf{T}$ in that case is equivalent to calling this residual function with T as input, which may be both simpler and faster.
That's for instance how JNFK approaches work.
I can come back later to give a simple Python approach of both approaches if that helps clarifying.
EDIT: I was typically thinking of the 1D heat transfer. Here is a small Python example that shows to ways of semi-discretising the PDE with finite differences, with Neumann conditions on the sides:
import numpy as np
from scipy.sparse import diags
N = 50 # number of mesh points
L=1 # domain length
dx = 1/(N-1) # space step
T0=np.linspace(0,1,N)**2 # initial solution
# 1st approach, form the Laplacian matrix
A = diags([1,-2,1],[1,0,-1], shape=(N,N)).toarray() # not good, drops the sparse character
A[0,0]=-1 # Neumann condition
A[-1,-1]=-1
A = A/dx**2
def dTdt_matrix(T):
return A.dot(T)
# 2nd approach, directly compute the terms pointvby point
def dTdt(T):
dTdt = np.zeros((T.size,))
dTdt[1:-1] = (1/dx**2) * ( T[:-2] - 2*T[1:-1] + T[2:] )
dTdt[0] = (1/dx**2) * ( T[1] - T[0] )
dTdt[-1] = (1/dx**2) * ( T[-2] - T[-1] )
return dTdt
assert np.allclose( dTdt(T0), dTdt_matrix(T0) )
The second approach is faster, and is very easy to code. It is also more error proof in my experience (generalising to 2D for instance). This is even more true if the time derivative is a nonlinear function. Note however that there are instances where it may be needed anyway, or at least approximate the Jacobian of $f$ (here $A$), for instance for implicit time integration. Jacobian-free Newton-Krylov approaches do not compute $A$, but only matrix vector products which can be obtained with the model function directly. $A$ or some approximation of $A$ may however be needed for preconditioning.