I would just like to clarify that:

defguard is_even(value) when is_integer(value) and rem(value, 2) == 0


is not equivalent to:

defmacro is_even(value), do: is_integer(value) and rem(value, 2) == 0


First of all, because that macro will return true and false at compile
time, which is not what we want. :)

But most importantly, it is because the most obvious implementation has
some pitfalls:

defmacro is_even(value) do
  quote do
    is_integer(unquote(value)) and rem(unquote(value), 2) == 0

  end

end


The code above unquotes the value twice, which means that when used outside
of a guard, such as in "if is_even(some_expr())", some_expr is evaluated
twice. One option would be to use bind_quoted or similar but keep in mind
we can't define variables inside guards so we need to account for both
cases. The final solution would look like this:

defmacro is_even(value) do

  if __CALLER__.context == :guard do

    quote do
      is_integer(unquote(value)) and rem(unquote(value), 2) == 0

    end
  else
    quote do
      value = unquote(value)

      is_integer(value) and rem(value, 2) == 0

    end

end


Which is quite complex compared to:

defguard is_even(value) when is_integer(value) and rem(value, 2) == 0



*José Valimwww.plataformatec.com.br
<http://www.plataformatec.com.br/>Founder and Director of R&D*

On Wed, Sep 27, 2017 at 6:10 PM, OvermindDL1 <[email protected]> wrote:

> Also see past discussion (or lack there-of really, only comments about it
> was someone saying how they would love such functionality) at:
> https://elixirforum.com/t/defguard/4052
>
>
> On Wednesday, September 27, 2017 at 10:09:31 AM UTC-6, OvermindDL1 wrote:
>>
>> As requested at https://github.com/elixir-lang/elixir/pull/5857#issuecomm
>> ent-332563628 this is created.
>>
>> Currently there is a `defguard` being created for elixir 1.6.0 that is
>> nothing more than a macro and does no extra functionality there-of.  It (as
>> far as my reading shows) just allows:
>>
>> ```elixir
>> defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
>> ```
>>
>> Which can be used like:
>>
>> ```elixir
>> *def steps(n) when n > 0, do: steps(n, 0) defp step(1, step_count), do:
>> step_count defp step(n, step_count) when is_even(n), do: step(div(n, 2),
>> step_count + 1) defp step(n, step_count), do: step(3*n + 1, step_count + 1)*
>> ```
>>
>> Which is really no gain over just using a macro like:
>>
>> ```elixir
>> defmacro is_even(value), do: is_integer(value) and rem(value, 2) == 0
>> ```
>> Other than just verify that only proper guards are used (which could just
>> be another macro that verifies that too).
>>
>> Instead, a while back, I made a library called `defguard`:
>> https://github.com/overminddl1/defguard
>>
>> Disclaimer: It is just an example of something that should be, in my
>> opinion, built in to Elixir's `def`/`defp`/`defmacro`/`defmacrop`, right
>> now when it is used it just replaces the built-in `def` calls with its own
>> macro version that just delegates back down after performing expansion.  It
>> does not make a good standalone library and that is why I have very
>> purposefully not finished it to an extent to be generically useful
>> (replacing `def` and so forth is not good form in my opinion).
>>
>> Now what it does is you can do:
>>
>> ```elixir
>> defguard is_struct(%{__struct__: struct_name}) when is_atom(struct_name)
>> defguard is_struct(%{__struct__: struct_name}, struct_name)
>> defguard is_exception(%{__exception__: true} = exc) when is_struct(exc)
>> ```
>>
>> Which can then be used like:
>>
>> ```elixir
>> def blah(any_exc) when is_exception(any_exc), do: any_exc
>> def blah(specific_struct) when is_struct(specific_struct, Specific), do:
>> specific_struct
>> def blah(any_struct) when is_struct(any_struct), do: any_struct
>> ```
>>
>> Note that this is something that you *cannot* do with the current
>> `defguard` proposal in Elixir slated for 1.6.0, that is structural
>> matching, I.E. testing the structure of the values and being able to pull
>> out and test the data inside, which is what you cannot do with the current
>> proposal, notable with maps and structures and other deep constructs become
>> significantly easier.
>>
>> However, unlike just creating a `defguard` macro that then creates other
>> macro's, that should still be done as my example library does (although it
>> should generate two versions of it I'd say, see below), however the
>> difference is that the definitions of `def` and the others needs to have an
>> extra expansion phase.  In essence they would need to be changed so that if
>> any guard is called that is not one of the valid guard types then it should
>> be expanded (the macro is called) but instead of it called, say for a given
>> `is_struct` for a macro it would normally expand the function named
>> :"MACRO-is_struct" it should instead expand a function named
>> `:"GUARD-is_struct"` or something of that style if it exists, then it takes
>> the return information and mixes in both the structural part into the
>> correct location in the argument and mixes in the guards into the `when`
>> guards section of the head.  If `is_struct` were called in any other place
>> than the function head then it would expand via the normal macro call
>> (perhaps even function? I'd opt for a macro return though) that does the
>> structural test and guard tests and returns true/false as appropriate.
>>
>> Thus, `defguard` would need to generate at least 2 functions for each
>> guard so it is useful in every possible location, and
>> `def`/`defp`/`defmacro`/`defmacrop`/`case` and maybe `cond` should have
>> their ast updated to handle that expansion into the heads/cases.  This then
>> makes it ubiquitous through-out elixir-the-language and let's people define
>> new guards that are significantly more powerful than what a `defmacro`
>> version of the `defguard` should be capable of otherwise.  I.E. `defguard`
>> should only be added if it actually adds functionality over an equivalent
>> `defmacro` or there is still no purpose to its existence other than just
>> being a `defmacro` that shuffles it's guards into its body, which really
>> gains extremely little (even the readability gain is minor, see the top
>> example and comparison).
>>
> --
> You received this message because you are subscribed to the Google Groups
> "elixir-lang-core" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> To view this discussion on the web visit https://groups.google.com/d/
> msgid/elixir-lang-core/eec36778-b463-4b67-830f-
> 7f572fb59732%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/eec36778-b463-4b67-830f-7f572fb59732%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4LxECd_oC6PrG_RZoNxteOUV3VAbU0HKJ8yYwhb_JU-SA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to