JuliaServices / Match.jl

Advanced Pattern Matching for Julia
https://juliaservices.github.io/Match.jl/latest/
Other
240 stars 22 forks source link

problem with v"0.1.3"? #19

Closed william-macready closed 1 year ago

william-macready commented 9 years ago

Hi Kevin

the following code snippet: using Match

function transform(ast::Expr)
@show ast
    @match ast begin
        Expr(:call,[:aSent,predName::Symbol],_) => Expr(:ref,predName, 1)
        Expr(:call,[:bSent,arg1::Expr,symbol("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

e = :(bSent(aSent(C),&,aSent(A)))
transform(e)

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!

kmsquire commented 9 years 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 Exprs.

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!

kmsquire commented 9 years ago

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.)

william-macready commented 9 years ago

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!

kmsquire commented 9 years ago

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!

william-macready commented 9 years ago

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.

kmsquire commented 9 years ago

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.

william-macready commented 9 years ago

i see. thanks

gafter commented 1 year ago

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])