How let a function be used in a guard like is_binary/1

I noticed that some function like is_binary/1 is marked as “Allowed in guard tests”. How can I make my own functions/guards like that?

If I defguard and def the same name, the compiler complains.

Thanks!

2 Likes

You can’t, guards are fundamentally defined in the VM itself, which allows the VM to perform specific optimizations. defguard lets you essentially construct “new” guards by composing together other guards, but you can’t make new “from scratch” guards.

6 Likes

I think I worded my question poorly, but anyways, the docs clearly explain how to do what I want…

(defguard) creates a macro that can be used both inside or outside guards.

defmodule Foo do
  
  defguard bar(m) when is_map(m) and m.field == true
  
end

iex> require Foo
iex> Foo.bar(%{field: true})
true

I just needed to read more better… :slight_smile:

4 Likes

You can do it even without defguard - macros can be expanded in guards, and that is how custom guards could be done way before defguard became a thing.

1 Like

:thinking:

I guess I have a follow up question…

defguard is_automatch(thing) when
    (is_struct(thing, Opportunity) and thing.type == "Opportunity::Automatch")
    or (is_struct(thing, Category) and thing.type == "Category::Automatch")

Is there anyway to get that guard to raise when used on something that isn’t an Opportunity or Category?

Like I want this to raise with a no-clause error instead of returning false:

iex> require Typing
iex> Typing.is_automatch(%{})
false

But only when used as a function and not as a guard. Or if that’s not possible, raise in both cases (guard or function) if you pass the wrong type of thing in.

defguard is_automatch(thing) when
    (is_struct(thing, Opportunity) and thing.type == "Opportunity::Automatch")
    or (is_struct(thing, Category) and thing.type == "Category::Automatch")
    or :fail

It will raise in both guard and function, but raising in guard mean probably something different than what you expect.

2 Likes

Yes, it just means that the guard fails and that clause is not chosen.

2 Likes

I don’t see the value of guards over this:

def do_stuff(%Opportunity{type: "Opportunity::Automatch"}), do: ...
def do_stuff(%Category{type: "Category::Automatch"}), do: ...

…and nothing else. Elixir will give you a runtime error if you pass anything else because there’s no clause to handle it. :person_shrugging:

6 Likes

I think guards provide a lot of readability in some cases. Plus it’s nice to be able to use same guard name inside an if or cond statement.

Also I’m just experimenting with types and polymorphism. And I think I’m finding out guards aren’t meant to be used in the way I’m thinking.

Like you can’t replicate this with guards:

def is_automatch(%Opportunity{type: type}), do: type == "Opportunity::Automatch"
def is_automatch(%Categeory{type: type}), do: type == "Category::Automatch"

iex> is_automatch(%User{})
FunctionClauseError

Cuz the whole point of guards is to keep trying to one of them hits; you can’t say “if some condition in this guard happens, halt early and raise an error.” I.e. it’s impossible for a guard to crash because the wrong type was passed to it.

I don’t object to anything you said, I am just wondering what kind of code you’re looking to write in the end, hence me earlier reply.