Pattermatching on functions/constants

132 views
Skip to first unread message

Nytz12

unread,
May 22, 2016, 6:16:38 AM5/22/16
to elixir-lang-talk
Hi

I use the following pattern to define cross module constants

defmodule Visitor do

  defmodule
FilterTypes do
   
def c_GREATER_THAN, do: "greater_than"
   
def c_LESS_THAN,    do: "less_than"
   
def c_IS,           do: "is"
   
def c_IS_NOT,       do: "is_not"
 
end
 
...

Which lets me do something like this from other modules

Visitor.FilterTypes.c_GREATER_THAN # == "greater than"

Problem is that I just discored that these 'constants', which are really function calls, cannot be used to do pattern match like:

def filter(query, filter, field, value) do
   
case filter do
     
FilterTypes.c_GREATER_THAN -> greater_than(query, field, value)
     
FilterTypes.c_LESS_THAN    -> less_than(query, field, value)
     
FilterTypes.c_IS           -> is(query, field, value)
     
FilterTypes.c_IS_NOT       -> is_not(query, field, value)
      _                          
-> {:error, "unknown filter", filter}
   
end
 
end

Is there a better approach i'm missing?

Kip Cole

unread,
May 22, 2016, 6:21:40 AM5/22/16
to elixir-lang-talk
You might consider using Module attributes.  These are a compile time only feature but it looks like you're doing matching that would work.  So you would do something like:

defmodule Visitor do
  @greater_than "greater_than"
  @less_than "less_than"
  
  def filter(query, filter, field, value) do
    case filter do
      @greater_than -> greater_than(query, field, value)
      @less_than    -> less_than(query, field, value)
      ....
    end
  end
end

Kip Cole

unread,
May 22, 2016, 6:30:30 AM5/22/16
to elixir-lang-talk
Also probably more idiomatic to use pattern matching for this.  

defmodule Visitor do
  @greater_than "greater_than"
  @less_than "less_than"
  
  def filter(query, @greater_than, field, value),
    do: greater_than(query, field, value)
  def filter(query, @less_than, field, value),
    do: less_than(query, field, value)
end

And if it really is just text-based dispatching you could, conceivably, so a dynamic dispatch like:

defmodule Visitor do
  def filter(query, filter, field, value) do
    apply(__MODULE__, filter, [query, field, value])
  end
end
 
But thats not very transparent and a bit slower since the dispatch is discovered at runtime

On Sunday, May 22, 2016 at 6:16:38 PM UTC+8, Nytz12 wrote:

max s

unread,
May 22, 2016, 6:35:24 AM5/22/16
to elixir-l...@googlegroups.com
Hi,

I use macros for such requirement in my own code:

defmodule Constants do
  defmacro val1 do
    quote do
      "val1"
    end
  end

  defmacro val2 do
    quote do
      "val2"
    end
  end
end

And here's how it's used:

defmodule Test do
  require Constants
  
  def match v do
    case v do
      Constants.val1 -> "matched val1"
      Constants.val2 -> "matched val2"
      _ -> "no match"
    end
  end
end

Max

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/ace6b869-dfce-4d3f-aeeb-2d9bc4583f03%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nytz12

unread,
May 22, 2016, 9:54:24 AM5/22/16
to elixir-lang-talk
I see, not too fond of how much boilerplate is required for each constant, but perhpas this is the only way to get them in guards?

max s

unread,
May 22, 2016, 10:25:18 AM5/22/16
to elixir-l...@googlegroups.com
Well that is how I do it. Obviously the listing I sent is the detailed one, nothing keeps you from writing a meta-macro to generate the sub macros:

defmodule Constants do
  defmacro c name, value do
    quote do
      defmacro unquote(name) do
        unquote(value)
      end
    end
  end
end

And use it like that to define your constants:

defmodule Constants.Vals do
  require Constants
  
  Constants.c val1, "val1"
  Constants.c val2, "val2"
end

To finally use them in your code:

defmodule Test do
  require Constants.Vals
  
  
def match v do
    case v do
      Constants.Vals.val1 -> "matched val1"
      Constants.Vals.val2 -> "matched val2"
      _ -> "no match"
    end
  end
end

Metaprogramming makes you able to introduce any syntactic sugar you like (more or less), you should take advantage of it.

Max

Onorio Catenacci

unread,
May 23, 2016, 8:23:41 AM5/23/16
to elixir-lang-talk
You could do something like this:

def filter(query, filter, field, value) do

    
if (filter == FilterTypes.c_GREATER_THAN),  do: greater_than(query, field, value)
    if (filter == FilterType.c_LESS_THAN), do: less_than(query, field, value)
# etc. etc.
  end

I'm not sure that's a vastly superior alternative but I think it would work.

--
Onorio

max s

unread,
May 23, 2016, 11:21:41 AM5/23/16
to elixir-l...@googlegroups.com
It's an alternative, but I would not recommend it. 

Apart from the performance hit (branching and function call instead of benefiting from the optimizations that come with matching on constant values), it's not very elegant and does not express the intent of the reduction in a functional way.

Max

Onorio Catenacci

unread,
May 23, 2016, 11:38:17 AM5/23/16
to Elixir Lang Talk
What performance hit is that?  Have you profiled the code?  Assuming that this would perform worse than any other code is an assumption I'm not willing to make absent some profiler output to show me that it actually performs worse. Premature optimization, etc. etc.

I'll grant that it's not beautiful code but I disagree with the a priori assumption that it would automatically perform worse than any other alternative. 




--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/l-7rK9_bT2Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/CAOPKHes6YJYUsEK_%3DS%2B7bW4ptiJfwN3rNkFiHTxVpdX-z78MUw%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.

José Valim

unread,
May 23, 2016, 11:45:30 AM5/23/16
to elixir-l...@googlegroups.com
Why not use atoms? Atoms are precisely that: constants.



José Valim
Skype: jv.ptec
Founder and Director of R&D

--

max s

unread,
May 23, 2016, 12:35:26 PM5/23/16
to elixir-l...@googlegroups.com
@Onorio

You're right, we should not take assumptions before benchmarking even though I think my intuition was justified. I wrote a quick code that I profiled with fprof and I get 3~4 better performances using the matching on constants ; you can see the gist here https://gist.github.com/anonymous/3cf62215407b829da4ab013d111cebfe (let me know and correct me if there's a mistake in my profiling).

@José

Agreed and actually it'd be more in line with erlang philosophy of doing things

Max

Onorio Catenacci

unread,
May 23, 2016, 1:32:47 PM5/23/16
to Elixir Lang Talk
Definitely looks like empirical proof to me :)

--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/l-7rK9_bT2Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/CAOPKHev-O0ZdDURxp144Xp4UrT-0FWECVYfNRwxyrg6HRkP0vQ%40mail.gmail.com.

For more options, visit https://groups.google.com/d/optout.



--

max s

unread,
May 23, 2016, 2:10:08 PM5/23/16
to elixir-l...@googlegroups.com
The theoretical assumption was that pattern matching on constants is much faster than function calls and branching (if you like some literature, this chapter of Jones' book provides some good insight).
The empirical proof was running fprof, an erlang tool "used to profile a program to find out how the execution time is used".

Further analysis of the compiled bytecode or better yet, the actual machine code and CPU instructions produced by the BEAM, is left as an exercise to the reader.


Max

José Valim

unread,
May 23, 2016, 3:22:01 PM5/23/16
to elixir-l...@googlegroups.com
The empirical proof was running fprof, an erlang tool "used to profile a program to find out how the execution time is used".

To be fair, fprof should not be used for benchmarking because the profiling hooks will affect code performance in different ways. For example, if you are running code and then enables fprof, that same code path may now take much longer. A benchmarking tool would be ideal here.

Onorio Catenacci

unread,
May 23, 2016, 3:41:30 PM5/23/16
to Elixir Lang Talk
Hmm.  I always had the impression that fprof was the preferred way to benchmark code.  Good to know.

--
Onorio


On Mon, May 23, 2016 at 3:21 PM, José Valim <jose....@plataformatec.com.br> wrote:
The empirical proof was running fprof, an erlang tool "used to profile a program to find out how the execution time is used".

To be fair, fprof should not be used for benchmarking because the profiling hooks will affect code performance in different ways. For example, if you are running code and then enables fprof, that same code path may now take much longer. A benchmarking tool would be ideal here.

--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-talk" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-talk/l-7rK9_bT2Q/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-ta...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Nytz12

unread,
May 24, 2016, 5:48:31 AM5/24/16
to elixir-lang-talk, jose....@plataformatec.com.br
Two things

1: That doesn't solve the issue of sharing constants between modules
2: I use strings because I match it against user supplied input that I do not want to convert to atoms

Correct me if im missing something

Mads

José Valim

unread,
May 24, 2016, 6:02:42 AM5/24/16
to Nytz12, elixir-lang-talk

1: That doesn't solve the issue of sharing constants between modules

You can just use atoms in both places.
 
2: I use strings because I match it against user supplied input that I do not want to convert to atoms

You can write a straight-forward conversion function:

def from_user_input("less_than"), do: :less_than
def from_user_input("greater_than"), do: :greater_than
...

Mads Hargreave

unread,
May 24, 2016, 6:15:07 AM5/24/16
to José Valim, elixir-lang-talk
1:

The thing with the constants is I get a compile time check that I am referencing the exact string/atom.

Say, Module A i reference :less_than and in module B :less_then (typo). This error will only be shown (silently) at runtime.

2:

This seems like a lot of boilerplate as I may have 20 + of those filters 

José Valim

unread,
May 24, 2016, 6:28:07 AM5/24/16
to Mads Hargreave, elixir-lang-talk
Say, Module A i reference :less_than and in module B :less_then (typo). This error will only be shown (silently) at runtime.

It should not be silent if you are relying on pattern matching and do not use catch-all clauses. Although you do not have compile time guarantees indeed. 

This seems like a lot of boilerplate as I may have 20 + of those filters 

It goes without saying it could be automated with macros. :)

Mads Hargreave

unread,
May 24, 2016, 6:35:31 AM5/24/16
to José Valim, elixir-lang-talk
I see

Yeah my bad, I should definitely familiarise myself more with meta-programming 
Reply all
Reply to author
Forward
0 new messages