Closed william-macready closed 1 year ago
Okay, unfortunately, the reason this worked before and is failing now is directly related to the cleanup I just did.
In your expression here, symbol("f")
is a function call (which happens to return :f
). In v0.0.6 (and earlier), I was attempting to determine the whether anything that looked like a call was a function or a type. Since the AST that's passed to the macro doesn't have this information, the way I attempted to handle this was to use eval
.
Generally, this causes problems and is somewhat brittle: we may try to eval a symbol that isn't yet defined (#12), evaluation happens in global scope (because the local scope doesn't even really exist yet), and eval
causes performance problems. Also, macros are really just meant for code transformations.
So, I got rid of eval
, and because of how removed eval
, everything that looks like a function call is now treated as a potential Type
to be matched against. So any function calls that are not Type
constructors will fail, as you found out.
So, the easiest thing is to ask if you can remove any function calls not matching a Type from your code from the left side of a match, or if you can stick with v0.0.6.
Brainstorming:
One option would be to generate code which would always checks whether a call is a function call or a type constructor, and simply passes through function calls. This has a few downsides--the test would, I hope, be eliminated by the compiler, but generating the code is tricky, and would probably become pretty complicated.
Another potential way forward would be to limit the types that can be matched against in @match
. Zach Allaun's Match.jl
(pointed to on the front page) required types to be annotated with @matchcase
in order to be matched, and Scala only matches against case classes
(I think). This is a possibility, but it's a big change in behavior, and wouldn't actually help you here, because it would preclude matching against Expr
s.
One thing that might work would be to test the parameters of the call, and if they are all constant, let the call through. I think this still has problems--if the call is a type constructor, you often don't want to create a new object every time you want to match against it.
Another option would be to consider the ideas in #15.
If you (or anyone) has any thoughts or suggestions for ways this might work, please post them here.
Cheers!
To be clearer, you would only have to remove function calls which are not matching against a Type
on the left side of a =>
. Type constructors (or pseudo-constructors like Expr(...)
) are fine on the left, and everything is fine on the right.
(Fixed inline above.)
I'll do as you suggest and remove the function calls. In my explorations of Julia thus far, I haven't yet touched macros, so I'm afraid I'm of little help to you.
Thanks for the explanation, and a very useful package Kevin!
Glad you find it useful! I'll leave this open for now in case someone has similar issues and might have some suggestion going forward. I suspect that the best solution is to attempt to integrate this functionality directly into the Julia parser, but I'm not going to be able to attempt this any time soon.
It would be good to add this to the "Notes/Gotchas" section in the docs. I'll probably get to this eventually, but if you're up for it, a pull request would be gladly accepted.
Cheers!
Here's a thought. It appears that functions can appear on the left of => as long as they are in the pattern guard. So could @match rewrite the offending
Expr(:call,[:bSent,arg1::Expr,symbol("f"),arg2::Expr],_) => Expr(:comparison,transform(arg1),:implies,transform(arg2))
into:
Expr(:call,[:bSent,arg1::Expr,f::Symbol,arg2::Expr],_), if f==symbol("f") end => Expr(:comparison,transform(arg1),:implies,transform(arg2))
I guess to do this you'd need to recognize function calls on the left hand side, but can't you do this by pulling apart the Expr supplied to the macro. Sorry if this makes no sense, as I said, I haven't played with macros yet.
Unfortunately, that won't work. The problem is that, from the macro's perspective, there's no easy way to distinguish type constructors from function calls. All the macro sees is Expr(:call, [:MyType, arg1, arg2], )
or Expr(:call, [:somefunc, arg1, arg2])
.
Originally, I was calling eval
on MyType
and somefunc
to determine whether it was a type or function call. But there are a number of problems with this (as I outlined above).
Thanks for the thought, though--I'm open to other suggestions you might have.
i see. thanks
You can address this in version 2 as folllows:
julia> Pkg.status()
Status `~/.julia/environments/v1.6/Project.toml`
[7eb4fadd] Match v2.0.0
julia> function transform(ast::Expr)
@show ast
@match ast begin
Expr(:call,[:aSent,predName::Symbol]) => Expr(:ref,predName, 1)
Expr(:call,[:bSent,arg1::Expr,:f,arg2::Expr]) => Expr(:comparison,transform(arg1),:implies,transform(arg2))
Expr(:call,[:bSent,arg1::Expr,:(&),arg2::Expr]) => Expr(:call,:(&), transform(arg1), transform(arg2))
_ => error(":Unknown expression $ast")
end
end
transform (generic function with 1 method)
julia> e = :(bSent(aSent(C),&,aSent(A)))
:(bSent(aSent(C), &, aSent(A)))
julia> transform(e)
ast = :(bSent(aSent(C), &, aSent(A)))
ast = :(aSent(C))
ast = :(aSent(A))
:(C[1] & A[1])
Hi Kevin
the following code snippet: using Match
complains with ERROR: type: isa: expected Type{T<:Top}, got Function
On v"0.0.6" everything runs fine. Interestingly, replacing symbol("f") with :f works on v"0.1.3"
thanks for looking into this!