Skip to content

Commit

Permalink
docs for programmatic formula and informative error message for $
Browse files Browse the repository at this point in the history
  • Loading branch information
kleinschmidt committed Jun 13, 2018
1 parent d4ae815 commit bab6287
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
48 changes: 48 additions & 0 deletions docs/src/formula.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,54 @@ julia> Formula(StatsModels.Terms(@formula(y ~ 1 + (a+b) * c)))
Formula: y ~ 1 + a + b + c + a & c + b & c
```

### Constructing a formula programmatically

Because a `Formula` is created at compile time with the `@formula` macro,
creating one programmatically means dipping into Julia's
[metaprogramming](https://docs.julialang.org/en/latest/manual/metaprogramming/)
facilities.

Let's say you have a variable `lhs`:

```jldoctest
julia> lhs = :y
:y
```

and you want to create a formula whose left-hand side is the _value_ of `lhs`,
as in

```jldoctest
julia> @formula(y ~ 1 + x)
Formula: y ~ 1 + x
```

Simply using the Julia interpolation syntax `@formula($lhs ~ 1 + x)` won't work,
because `@formula` runs _at compile time_, before anything about the value of
`lhs` is known. Instead, you need to construct and evaluate the _correct call_
to `@formula`. The most concise way to do this is with `@eval`:

```jldoctest
julia> @eval @formula($lhs ~ 1 + x)
Formula: y ~ 1 + x
```

The `@eval` macro does two very different things in a single, convenient step:

1. Generate a _quoted expression_ using `$`-interpolation to insert the run-time
value of `lhs` into the call to the `@formula` macro.
2. Evaluate this expression using `eval`.

An equivalent but slightly more verbose way of doing the same thing is:

```jldoctest
julia> formula_ex = :(@formula($lhs ~ 1 + x))
:(@formula y ~ 1 + x)
julia> eval(formula_ex)
Formula: y ~ 1 + x
```

### Technical details

You may be wondering why formulas in Julia require a macro, while in R they
Expand Down
4 changes: 4 additions & 0 deletions src/formula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ is_call(::Any) = false
is_call(::Any, ::Any) = false
check_call(ex) = is_call(ex) || throw(ArgumentError("non-call expression encountered: $ex"))

catch_dollar(ex::Expr) =
Meta.isexpr(ex, :$) && throw(ArgumentError("interpolation with \$ not supported in @formula. Use @eval @formula(...) instead."))

mutable struct Formula
ex_orig::Expr
ex::Expr
Expand Down Expand Up @@ -252,6 +255,7 @@ function parse!(i::Integer, rewrites)
end
function parse!(ex::Expr, rewrites::Vector)
@debug "parsing $ex"
catch_dollar(ex)
check_call(ex)
# iterate over children, checking for special rules
child_idx = 2
Expand Down

0 comments on commit bab6287

Please sign in to comment.