Warning when using function variants with different keyword options

I am a newbie to Elixir so please forgive any foolishness.

I have an ‘outer’ function called foo that needs to provide two main variants, that I want to be selected using a single-entry Keyword list, e.g. do_it: :one, or do_it: :two, with :two being the default case.

The two variants are such that one is a trivial extension of the other, so that the extended form can call the non-extended form, after performing a small amount of pre-processing on the input argument.

I have attempted to implement this behaviour as follows:

  # the extended case, indicated by the option: keyword
  def foo(value, do_it: :one) do
    # do some pre-processing

    # then call the 'core' processing, with no keyword
    foo(val)
  end

  # the non-extended case, called by the extended case, keyword defaults to nil
  def foo(value, _opt \\ nil) do
	  #  do the main work
  end

  # the 'parent' case, which may optionally be passed the do_it: keyword pair
  def bar(value, opt \\ nil) do
	case foo(value, opt) do
		case1 -> ...
		case2 -> ...
	end	
  end
  
  # a typical call, invoking the extended case
  bar(my_value, do_it: :one)

Unfortunately, when I use this approach, I get the following compiler warning.

warning: def foo/2 has multiple clauses and also declares default values. In such cases, the default values should be defined in a header. Instead of:

    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

one should write:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

Now, I am sure that this explanation is meant to clarify things, but to me it is just
adding confusion. I realise it’s a big ask, but could somebody sugest how my code should
be structured to avoid this warning? The suggested improvment just doesn’t make
sense to me - e.g. what are :first_clause and :second_clause??

Hello and welcome,

The easiest way to fix this would be…

  def foo(value, opt \\ nil)
  def foo(value, do_it: :one) do
    # do some pre-processing

    # then call the 'core' processing, with no keyword
    foo(val)
  end

  # the non-extended case, called by the extended case, keyword defaults to nil
  def foo(value, opt) do
	  #  do the main work
  end

But it might not be the best way.

First, nil is not a keyword. I would rather use

opt \\ []

to ensure return type is a keyword.

Using _opt means You don’t care about opt… another way to fix it is to use arity of 1

def foo(value) do

This would also solve the problem, if the core function does not have params.

Maybe this?

  def foo(value, opt \\ [])
  def foo(value, do_it: :one) do
    # do some pre-processing

    # then call the 'core' processing, with no keyword
    foo(val)
  end

  # the non-extended case, called by the extended case, keyword defaults to nil
  def foo(value, opt) do
	  #  do the main work
  end

BTW You should not think in OOP terms, it is not inheritance, it’s composition. You compose functions to make a bigger one. Not function A is extending function B.

1 Like

Hi Koko, thanks for your prompt reply, which makes a lot of sense. Unfortunately, first time I read your suggestion, I missed the point about having two ‘declarations’ of the defaulted case. I’ve never seen this done before (I told you I was a newbie :stuck_out_tongue:). Definitely, something to research!

I take your point wrt OOP although I’m not sure that one function extending another is really a symptom of this - that just how my logic works in this case. I appreciate how the terminology may be confusing though - especially used to the word ‘extend’. Fwiw, I do sometimes code in an OOP way: muscle memory more than anything else :slight_smile: In particular, i keep typing things like snafu.map(fn) rather than Enum.map(snafu,fn) :frowning: Blame Scala for that…