15 Jul 2013, 03:42
Dave_gnome_head_isolated_pragsmall

Dave Thomas (342 posts)

  • The Elixir test framework, ExUnit, uses some clever code quoting tricks. For example, if you assert

    assert 5 < 4
    

    You’ll get the error “expected 5 to be less than 4.”

    The Elixir source code is on Github (at

    https://github.com/elixir-lang/elixir

    ). The implementation of this is in the file /lib/ex_unit/lib/assertions.ex. Spend some time reading this file, and work out how it implements this trick.

    (Hard) Once you’re done that, see if you can use the same technique to implement a function that takes an arbitrary arithmetic expression and returns a natural language version.

    explain do: 2 + 3*4
    #=> multiply 3 and 4, then add 2
    
18 Aug 2014, 16:18
Mac 128k logic board 96x96_pragsmall

Roger Turner (7 posts)

explainer.ex

  defmodule Explainer do

    # A simple "natural language" form is a uniform sequence of verb-initial clauses:
    # - implies clauses like "subtract right from left", and similarly for other ops;
    # - "by" parameter used for "multiply by <number>" (compare "subtract <number>").

    defmacro make(op, name, from, by) do
      quote do
        def explainer({unquote(op), _, [left, right]}) 
            when is_number(left) and is_number(right) do
          unquote(name) <> "#{right} " <> unquote(from) <> "#{left}"
        end
        def explainer({unquote(op), _, [left, right]}) 
            when is_number(left) do
          "#{explainer right}, then " <> unquote(name) <> unquote(from) <> "#{left}"
        end
        def explainer({unquote(op), _, [left, right]}) 
            when is_number(right) do
          "#{explainer left}, then " <> unquote(name) <> unquote(by) <> "#{right}"
        end
        def explainer({unquote(op), _, [left, right]}) do
          "#{explainer left}, then #{explainer right}, then " 
          <> String.rstrip(unquote(name))
        end
      end
    end
  end

explain.ex

  defmodule Explain do

    require Explainer

    Explainer.make(:+, "add ",      "to ",   "")
    Explainer.make(:-, "subtract ", "from ", "")
    Explainer.make(:*, "multiply ", "by ",   "by ")
    Explainer.make(:/, "divide ",   "into ", "by ")

    defmacro explain(n) when is_number(n), do: "#{n}"

    defmacro explain {op, _, [left,right]} do
      explainer {op, nil, [left, right]}
    end

  end

  # explain 2 + 3 * 4 
  # => "multiply 4 by 3, then add 2”

  # explain (2+3)/(4-5*6) 
  # => "add 3 to 2, then multiply 6 by 5, then subtract from 4, then divide"
23 Sep 2014, 08:07
Patrick_pragsmall

Patrick Oscity (13 posts)

My solution:

defmodule Humanizer do
  defmacro generate(op, name, infix, suffix_addition \\ nil) do
    suffix = [name, suffix_addition] |> Enum.reject(&is_nil/1) |> Enum.join(" ")
    quote do
      def humanize({unquote(op), _, [lhs = {_, _, _}, rhs = {_, _, _}]}) do
        "first #{humanize lhs}, then #{humanize rhs}, then #{unquote(name)} both"
      end

      def humanize({unquote(op), meta, [lhs, rhs = {_, _, _}]}) do
        humanize({unquote(op), meta, [rhs, lhs]})
      end

      def humanize({unquote(op), _, [lhs = {_, _, _}, rhs]}) do
        "#{humanize lhs}, then #{unquote(suffix)} #{rhs}"
      end

      def humanize({unquote(op), _, [lhs, rhs]}) do
        "#{unquote(name)} #{lhs} #{unquote(infix)} #{rhs}"
      end
    end
  end
end

defmodule Explanations do
  require Humanizer

  Humanizer.generate :+, "add", "and"
  Humanizer.generate :+, "subtract", "from"
  Humanizer.generate :*, "multiply", "by", "by"
  Humanizer.generate :/, "divide", "by", "by"

  defmacro explain(code) do
    IO.puts humanize(code)
  end
end

defmodule Examples do
  require Explanations

  def run do
    Explanations.explain 1 + 2
    Explanations.explain 1 + 2*3
    Explanations.explain 2*3 + 1
    Explanations.explain 1*2 + 2*3
    Explanations.explain 5 * (2 + 3)
    Explanations.explain 5 * (2*2 + 3*3)
  end
end

Examples.run
# add 1 and 2
# multiply 2 by 3, then add 1
# multiply 2 by 3, then add 1
# first multiply 1 by 2, then multiply 2 by 3, then add both
# add 2 and 3, then multiply by 5
# first multiply 2 by 2, then multiply 3 by 3, then add both, then multiply by 5
21 Jan 2015, 20:58
Generic-user-small

Pierre Sugar (56 posts)

ddefmodule Translate do

  defmacro explain(expression) do
    construct((quote do: unquote(expression[:do])))
  end

  def construct({operator, _, [left, right]}) 
    when (is_number left) and (is_number right) do
      "#{translate(operator)} #{left} and #{right}"
  end

  def construct({operator, _, [left, right]}) 
    when (is_number left) and not (is_number right) do
      "#{construct(right)}, then #{translate(operator, 2)} #{left}"
  end

  def construct({operator, _, [left, right]}) 
    when not (is_number left) and (is_number right) do
      "#{construct(left)}, then #{translate(operator)} #{right}"
  end

  def construct({operator, _, [left, right]}) do
    "#{construct(left)} and #{construct(right)}, then #{translate(operator)}"
  end

  @operators [ {:+, "add"}, 
               {:+, " to"},
               {:-, "subtract"}, 
               {:-, " from"},
               {:*, "multiply"}, 
               {:*, " by"},
               {:/, "devide"},
               {:/, " by"}    ]

  def translate(operator, appendix \\ 1)
  def translate(operator, appendix) when appendix == 2 do
    @operators |> Keyword.take([operator]) |> Keyword.values |> List.to_string
  end

  def translate(operator, _appendix) do
    {:ok, word} = Keyword.fetch(@operators, operator)
    word
  end
end

defmodule TestTranslate do
  require Translate

  IO.puts Translate.explain do: 1+2*3
  IO.puts Translate.explain do: 1+2*(3+4)
  IO.puts Translate.explain do: (1+2)*(3+4)
  IO.puts Translate.explain do: (1+2)*(3+4)/5
  IO.puts Translate.explain do: 1+2
  IO.puts Translate.explain do: 2*(1+2)
  IO.puts Translate.explain do: 5-4*(3+2)-1
  IO.puts Translate.explain do: (5-4)+(3+2)
end

prints:

multiply 2 and 3, then add to 1                                                                                     
add 3 and 4, then multiply by 2, then add to 1                                                                                                                 
add 1 and 2 and add 3 and 4, then multiply                                                                                                                      
add 1 and 2 and add 3 and 4, then multiply, then devide 5                                                                                                      
add 1 and 2                                                                                                                                                     
add 1 and 2, then multiply by 2                                                                                                                                 
add 3 and 2, then multiply by 4, then subtract from 5, then subtract 1                                                                                          
subtract 5 and 4 and add 3 and 2, then add
  You must be logged in to comment