Solving a Permanent Income Model: OOP Approach¶
Our insight so far: hard-wiring functional forms does not work.
How can we build a more flexible model? One approach:
- define a
Model
object - it contains the fixed parameters \(T\), \(R\), \(Y\)
- plus another object that specifies preferences and their parameters
Then we can:
- init the model with the right preferences and parameters
- just pass the
Model
around (not the collection of parameters)
Factoring out Preferences¶
Utility functions are not model specific (certainly not in this case).
It would make sense to factor out all of the code that does utility calculations. Then we can:
- easily reuse that code in other projects
- test that code independently of everything else
- ensure that eacy utility function supports the same interface, so they can be easily swapped in and out.
What do we want utility functions to do?
- compute \(u(c)\) and \(u'(c)\)
- it is often also useful to know inverse marginal utility
- compute the growth rate of \(c\) and (related)
- Euler equation deviations
This defines the API that is visible to the outside world and common to all utility functions.
Note: If we were a bit more serious, we would think about embedding all of this into a bigger set of utility functions that depend on multiple arguments.
Let's start with log utility.
Log has no parameters, so we have
struct UtilityLog end
But for CRRA we have
struct UtilityCRRA
sigma :: Float64
end
Note: We generally would not want to hard-wire that sigma
is a Float64
. More generic would be a parametric type:
struct UtilityCRRA{T}
sigma :: T
end
But we keep things simple for now.
Since we are packaging the code, we will wrap it into a module
(which we will later make into a package).
Exercise: write code for the utility functions. My solution
Exercise: write tests for the utility functions. My solution
Making a Model¶
Now we easily build a Model
from its parts.
struct Model
Y :: Float64
R :: Float64
T :: Int
beta :: Float64
u :: AbstractUtility
end
m = Model(10.0, 1.04, 30, 0.98, UtilityLog());
Note:
- switching out the utility function is now trivial
- adding new utility functions is just as trivial
- the code does not contain any
if utilityLog
type switches
Exercise: Write this code - and don't forget the tests. My solution and the tests
I packaged everything in modules
because:
- eventually, this should go into a package (therefore into a module)
- once we define
structs
, we cannot repeatedlyinclude
the code (invalid redefinition of the struct) - the module makes sure that we don't have side-effects.
Note:
- Tracking changes when code lives in
module
s withRevise.jl
can be tricky. - Code needs to be included with
includet
(for tracking), not withinclude
. - Even then
Revise.jl
struggles with nestedincludet
s. - Once you have
module
s, it's always best to usepackage
s.