'Returning values from elixir functions inside modules

This is the method that processes an input string

def process(input) do
    list=String.split(input, "\n")

    f3=fn(a) ->
            String.split(a," ")
    end

    list=Enum.map(list, f3)

    func3=fn(n) ->
            length(n)==3
    end

    func2=fn(n) ->
            length(n)<=2
    end

    system=for x <-list, func3.(x), do: x

    input=for y <- list, func2.(y), do: y

    input=Enum.slice(input,0..length(input)-2)

    output=""

    output(input,output, system)
  end

This is the output function that uses recursion to edit a string and eventually return its value

def output(input, output, system) do
    cond do
      length(input)==0 ->
        output
      true ->
        [thing|tail]=input

        if length(thing)==2 do
          output=output<>"From "<>Enum.at(thing, 0)<>" to "<>Enum.at(thing,1)<>" is "<>Integer.to_string(calculate(thing, system))<>"km\n"
          output(tail, output, system)
        end

        if length(thing)==1 do
          if Enum.at(thing,0)=="Sun" do
                  output=output<>"Sun orbits"
                  output(tail, output, system)
          else
                  output=output<>orbits(thing, system)<>" Sun"
                  output(tail, output, system)
          end
        end

        output(tail, output, system)
    end
  end

As you can see when the input is an empty list it should return the output string. Using inspect shows that the output string does indeed have the correct value. Yet when the function is called in process(), it only returns the empty string, or nil.

Any help is appreciated, I am new to elixir so apologies if my code is a bit messy.



Solution 1:[1]

This could be a case where using pattern matching in the function head will let you avoid essentially all of the conditionals. You could break this down as:

def output([], message, _) do
  message
end

def output([[from, to] | tail], message, system) do
  distance = Integer.to_string(calculate(thing, system))
  new_message = "#{message}From #{from} to #{to} is #{distance} km\n"
  output(tail, new_message, system)
end

def output([["Sun"] | tail], message, system) do
  output(tail, "Sun orbits #{message}", system)
end

def output([[thing] | tail], message, system) do
  new_message = "#{message}#{orbits([thing], system)} Sun"
  output(tail, new_message, system)
end

This gets around some of the difficulties highlighted in the comments: reassigning output inside a block doesn't have an effect, and there aren't non-local returns so after an if ... end block completes and goes on to the next conditional, its result is lost. This will also trap some incorrect inputs, and your process will exit with a pattern-match error if an empty or 3-element list winds up in the input list.

I've renamed the output parameter to the output function to message. This isn't required – the code will work fine whether or not you change it – but I found it a little confusing reading through the function whether output is a function call or a variable.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Maze