Is it possible to use native functions names as callbacks on behaviour?

I can “overwrite” native stdlib functions, just by not importing them from the kernel, right?

Like:

defmodule Filter do
  Import Kernel, except: [not: 1]

  def not args do
    # ...
  end
end

Okay, that’s valid! But what if I wanted to put this function as a callback in a behavior, what would the syntax look like? Is that possible?

defmodule FilterBehaviour do
  Import Kernel, except: [not: 1]

  @callback not(args) :: any()
end

It doesn’t seem to work yet as a syntax error :thinking:

1 Like

(Have to preface it with: I would not recommend doing this)

Are you sure the syntax error you’re getting is not related to the args type being undefined? The following works for me:

defmodule FilterBehaviour do
  @callback not(any()) :: any()
end

defmodule Impl do
  @behaviour FilterBehaviour
  
  import Kernel, except: [not: 1]
  
  @impl FilterBehaviour
  def not(_args), do: :ok
end

In fact, yes, i do have sure its not related. I tried to built a simpler example but this is my current module:

defmodule Supabase.PostgREST.Query.FilterBehaviour do
  @moduledoc false

  import Kernel, except: [not: 1, or: 2, in: 2]

  @type column :: String.t()
  @type value :: term

  @callback eq(column, term) :: String.t()
  @callback neq(column, term) :: String.t()
  @callback gt(column, term) :: String.t()
  @callback lt(column, term) :: String.t()
  @callback gte(column, term) :: String.t()
  @callback lte(column, term) :: String.t()
  @callback is(column, term) :: String.t()
  @callback in(column, term) :: String.t()
end

looks at the in callback, it gives me the following syntax error:

== Compilation error in file lib/supabase/postgrest/query/filter_behaviour.ex ==
** (SyntaxError) lib/supabase/postgrest/query/filter_behaviour.ex:22:28: syntax error before: ')'
    |
 22 |   @callback in(column, term) :: String.t()
    |                            ^
    (elixir 1.14.4) lib/kernel/parallel_compiler.ex:340: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/7

TL;DR this works:

@callback unquote(:in)(column, term) :: String.t()

I can’t give you an intelligent answer as to why but I’m assuming it has to do with how def vs @callback get parsed. @callback clearly tries to parse in as an operator, even if it’s excluded.

That’s interesting! I was mislead a bit by or/2 being excluded, but no or callback being defined in the module - trying to define it also fails, so seems there’s something generally different about infix macros?

Defining it should work. You define infixes like so:

def a or b, do: a + b

I just don’t know how you would write that as a callback spec :thinking:

…and now that I said that:

@callback column() or term() :: String.t()

…also compiles so… there we go, ha.

3 Likes

Huh, works for both @callback and def. And since the implementing module is its own namespace of course, the import Kernel, except: [...] is not needed there.

defmodule Filter do
  @callback not(term) :: String.t()
  @callback term or term :: String.t()
  @callback unquote(:in)(term, term) :: String.t()
end

defmodule Impl do
  @behaviour Filter

  @impl Filter
  def not(a), do: "!#{a}"

  @impl Filter
  def a or b, do: "#{a} or #{b}"

  @impl Filter
  def unquote(:in)(a, b), do: "#{a} in #{inspect(b)}"
end

Impl.not(1) <> ", " <> Impl.or(1, 2) <> ", " <> Impl.in(3, [3, 4])

#=> "!1, 1 or 2, 3 in [3, 4]"

Soo, yeah… thanks for the TIL! :sweat_smile:

1 Like

You can also do left in right, no need for unquote. :slight_smile:

3 Likes