small medium large xlarge

Dave_gnome_head_isolated_pragsmall
15 Jul 2013, 03:42
Dave Thomas (381 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
    
Mac 128k logic board 96x96_pragsmall
18 Aug 2014, 16:18
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"
Patrick_pragsmall
23 Sep 2014, 08:07
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
Generic-user-small
21 Jan 2015, 20:58
Pierre Sugar (57 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
Generic-user-small
18 Jan 2016, 17:44
Stefan Chrobot (13 posts)

Here’s explain in Polish:

defmodule Natural do
  defmacro explain(expression) do
    explained = do_explain(expression)
    IO.puts(explained)
  end

  def do_explain({operator, _, args}) do
    explain_operator(operator, args)
  end

  defp explain_operator(operator, [left, right]) when is_number(left) and is_number(right) do
    { prefix, infix } = explain_operator(operator, :both)
    "#{prefix} #{left} #{infix} #{right}"
  end

  defp explain_operator(operator, [left, right]) when is_number(left) do
    "#{do_explain right}, potem #{explain_operator operator, :left} #{left}"
  end

  defp explain_operator(operator, [left, right]) when is_number(right) do
    "#{do_explain left}, potem #{explain_operator operator, :right} #{right}"
  end

  defp explain_operator(operator, [left, right]) do
    "#{do_explain left}, #{do_explain right}, potem #{explain_operator operator, :none}"
  end

  defp explain_operator(operator, arg_type) do
    case {operator, arg_type} do
      { :+, :both } -> { "dodaj", "i" }
      { :+, :right } -> "dodaj"
      { :+, :left } -> "dodaj"
      { :+, :none } -> "dodaj"
      { :-, :both } -> { "odejmij", "od" }
      { :-, :right } -> "odejmij"
      { :-, :left } -> "odejmij od"
      { :-, :none } -> "odejmij"
      { :*, :both } -> { "pomnoz", "i" }
      { :*, :right } -> "pomnoz razy"
      { :*, :left } -> "pomnoz razy"
      { :*, :none } -> "pomnoz"
      { :/, :both } -> { "podziel", "przez" }
      { :/, :right } -> "podziel przez"
      { :/, :left } -> "podziel z"
      { :/, :none } -> "podziel"
    end
  end
end

defmodule Test do
  require Natural
  import Natural

  def run do
    explain 1 + 2
    explain 1 - 2
    explain 1 * 2
    explain 1 / 2

    explain 1 + 2 + 3
    explain 1 - 2 - 3
    explain 1 * 2 * 3
    explain 1 / 2 / 3

    explain 2 + 3 * 4
    explain (2 + 3) * 4
    explain 1 / (2 + 3)

    explain 1 * 2 + 3 * 4

    explain 1 - 2 * (3 + 4) + 2
    explain 1 - (2 + 3) / (4 - 5 * 6)
  end
end

Test.run
20160310-gunnar_pragsmall
31 Jul 2016, 19:49
Felipe Juarez Murillo (9 posts)

I have had a little ashamed when I saw the above solutions, but here is mine, is not that sophisticated and does not handle some complicated elements but for the example, it works:

defmodule NaturalExpression do

  defmacro explain(clause) do
    do_clause   = Keyword.get(clause, :do, nil)
    IO.puts translate(do_clause)
  end

  defp translate({:*, _meta, [a, b]}) when is_tuple(a), do: translate(a) <> ", then multiply by #{b}"
  defp translate({:*, _meta, [a, b]}) when is_number(b), do: "Multiply #{a} and #{b}"

  defp translate({:/, _meta, [a, b]}) when is_tuple(a), do: translate(a) <> ", then divided by #{b}"
  defp translate({:/, _meta, [a, b]}) when is_number(b), do: "Divided #{a} and #{b}"

  defp translate({:+, _meta, [a, b]}) when is_tuple(a), do: translate(a) <> ", then add #{b}"
  defp translate({:+, _meta, [a, b]}) when is_number(b), do: "Add #{a} to #{b}"
  defp translate({:+, _meta, [a, b]}), do: translate(b) <> ", then add #{a}"

  defp translate({:-, _meta, [a, b]}) when is_tuple(a), do: translate(a) <> ", then rest #{b}"
  defp translate({:-, _meta, [a, b]}) when is_number(b), do: "Add #{a} to #{b}"
  defp translate({:-, _meta, [a, b]}), do: translate(b) <> ", then rest #{a}"

end

defmodule Test do
  import NaturalExpression

  explain do: 2 * 3 * 4

end
You must be logged in to comment