Skip to content

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 repeatedly include 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 modules with Revise.jl can be tricky.
  • Code needs to be included with includet (for tracking), not with include.
  • Even then Revise.jl struggles with nested includets.
  • Once you have modules, it's always best to use packages.