Optional argument function and multiple clauses and also declares default values

Hi all,

i have a module like this…

defmodule Fw.Code.String do

  def formatBy( string, opts ) when is_list( opts ) == false, do: formatBy( string, [ opts ] )
  def formatBy( string, opts \\ [] ) do
  
    splites = String.split( string, "{" )
    
    results =
      Enum.reduce( splites, [], fn( string, results ) ->
      
        if ( !String.contains?( string, "}" ) ) do
          results ++ [ string ]
        else
          
          [ formatKey, strB ] = String.split( string, "}" )
          { indexArg, notIntPart } =
            case Integer.parse( formatKey ) do
              { a, b }  -> { a, b }
              :error    -> { 0, formatKey }
            end
          
          arg =
            if ( notIntPart != "" ) do
              Keyword.get( opts, String.to_atom( formatKey ), nil )
            else
              Enum.at( opts, indexArg )
            end
            
            
          results =
            if ( arg != nil ) do
              results ++ [ arg ]
            else
              results
            end
        
            
          results ++ [ strB ]
        
        end

      end )
      
    Enum.join( results, "" )
    
  end
end
  

and here is unit test…

test "format" do
	
	#------------------------------------------
	# case 1
	#------------------------------------------
	input	= "123{0}456"
	expect	= "123456"
	
	result	= Fw.Code.String.formatBy( input )
	assert( expect == result, "result[#{ inspect result }] need match[#{ expect }]" )
	
	#------------------------------------------
	# case 2
	#------------------------------------------
	input	= "123{0}456"
	expect	= "123ZZ456"
	
	result	= Fw.Code.String.formatBy( input, [ "ZZ" ] )
	assert( expect == result, "result[#{ inspect result }] need match[#{ expect }]" )
	
	
	#------------------------------------------
	# case 3
	#------------------------------------------
	input	= "123{a}456"
	expect	= "123AAA456"
	
	result	= Fw.Code.String.formatBy( input, a: "AAA" )
	assert( expect == result, "result[#{ inspect result }] need match[#{ expect }]" )
	
	
	#------------------------------------------
	# case 4
	#------------------------------------------
	input	= "123{a}456{b}"
	expect	= "123AAA456BBB"
	
	result	= Fw.Code.String.formatBy( input, a: "AAA", b: "BBB" )
	assert( expect == result, "result[#{ inspect result }] need match[#{ expect }]" )
	
	
	#------------------------------------------
	# case 5
	#------------------------------------------
	input	= "123{0}456{1}"
	expect	= "123AAA456BBB"
	
	result	= Fw.Code.String.formatBy( input, [ "AAA", "BBB" ] )
	assert( expect == result, "result[#{ inspect result }] need match[#{ expect }]" )
	
end

it’s work method,
but i always get warning message…

warning: def formatBy/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

but i can’t switch functions order,
because it’s optional argument function,
if switch order the unit test will fail,
how can i fix this warning?

thanks

As the warning says, you need to add a function header to group default arguments.

You can read an example in getting started guide

2 Likes

i.e. try

def formatBy(string, opts \\ []) # i.e. formatBy(str) becomes formatBy(str, [])

def formatBy(string, opts) when not is_list(opts) do
  formatBy(string, [opts])
end

def formatBy(string, opts) do
  # ...
end
3 Likes

thanks everyone :slight_smile:

I typically follow a pattern whereby functions that may take variable options types first transform their options to some standard form. Typically for me into a map. So I hope you don’t mind but I reworked your code to follow that idiom. It puts more code up front transforming options at the public API interface but simplifies a lot the algorithm to be implemented.

defmodule Fw.Code.String do
  def format_by(string, opts \\ [])

  # String parameter
  def format_by(string, opts) when is_binary(opts) do
    format_by(string, %{"0" => opts})
  end

  # Keyword list parameters. We avoid String,to_atom calls
  # on data that might be user input.  Therefore we standardise
  # on String keys
  def format_by(string, [{_k, _v} | _rest] = opts) do
    opts =
      opts
      |> Enum.map(&{to_string(elem(&1, 0)), to_string(elem(&1, 1))})
      |> Map.new

    format_by(string, opts)
  end

  # Simple list parameter.  Add the index as a String
  def format_by(string, opts) when is_list(opts) do
    opts =
      opts
      |> Enum.with_index
      |> Map.new(&{to_string(elem(&1,1)), elem(&1, 0)})

    format_by(string, opts)
  end

  # Map parameter is our standard form after transformation
  # of parameters. There are definitely other ways to tokenise
  # the string to identify the parameters but using a regex with
  # captures makes it easy to pattern match later on.
  def format_by(string, opts) when is_map(opts) do
    string
    |> String.split(~r/[{}]/, include_captures: true, trim: true)
    |> replace_params(opts)
    |> Enum.join
  end

  # Simple body recursive function to replace parameters
  def replace_params([], _opts) do
    ""
  end

  def replace_params(["{", param, "}" | rest], opts) do
    param = String.trim(param)
    [Map.get(opts, param, "") | replace_params(rest, opts)]
  end

  def replace_params([head | rest], opts) do
    [head, replace_params(rest, opts)]
  end
end
2 Likes

wow, that’s awesome,
Your suggestion gave me more new view on the syntax level of Elixir.
thank you