In many ways, code generation is a natural comparison for type providers. However, type providers have several desirable properties that code generation lacks:
- Type providers can be used in F# scripts without ever having to context switch. With a code generator, you'd have to invoke the generator, reference the code, etc. With a type provider you reference the type provider assembly (which is just like referencing any other F#/.NET assembly) and then use the provided types right away. This is really a game changer for interactive scripting.
- As Gustavo mentions, erased types allow type providers to handle situations where traditional code generation would generate too much code (e.g. Freebase has thousands of types, which is no problem for a type provider).
- Type providers can support invalidation, so that if a data source changes the compiler will immediately recheck the file.
- Likewise, with a code generator it's possible for the generated code to get out of sync with the data source; type providers can prevent this problem from occurring, inspecting the data source each time your program is compiled (though many type providers also provide the option of using a cached schema for convenience).
- Type providers are arguably easier to implement, though it probably depends on the scenario and the author's background.