Skip to content

Instantly share code, notes, and snippets.

@toraritte
Last active March 20, 2017 03:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toraritte/7b617d4b4e1614f582037a252bc6bde1 to your computer and use it in GitHub Desktop.
Save toraritte/7b617d4b4e1614f582037a252bc6bde1 to your computer and use it in GitHub Desktop.
Saving different working use cases using Elixir metaprogramming
###################################################
### Solution to `invalid quoted expression`
### short version: use `Macro.escape/1`
###
### https://elixirforum.com/t/cant-seem-to-unquote-a-map/4009
###################################################
########################################
### (1) do smth with map, tuple etc.
### but only in the body of the
### dynamically defined function
########################################
defmodule A do
defmacro inject_map(arg) do
quote bind_quoted: [arg: arg] do
def balabab, do: unquote(arg)
end
end
end
defmodule C do
require A
A.inject_map(Macro.escape(%{lofa: 7}))
end
C.balabab #> %{lofa: 7}
##########################################
### (2) if map, tuple etc. needs massaging
### outside the dynamically defined
### function body
##########################################
defmodule A do
defmacro inject_map(arg) do
quote bind_quoted: [arg: arg] do
IO.inspect arg
#########################
# {:%{}, [], [lofa: 7]}
# so you can match it the last item of the tuple
# and restore it to map with `Enum.into/2`
# see https://github.com/society-for-the-blind/timesheets/blob/master/lib/timesheets/excel_date.ex
#########################
def balabab, do: unquote(arg)
end
end
end
defmodule C do
require A
A.inject_map(Macro.escape(%{lofa: 7}))
end
##########################################
### (3) to make it a complete circle,
### `IO.inspect/1` is now before `quote`
##########################################
defmodule A do
defmacro inject_map(arg) do
IO.inspect arg
#######################
# good luck with that
# {{:., [line: 5], [{:__aliases__, [counter: 0, line: 5], [:Macro]}, :escape]},
# [line: 5], [{:%{}, [line: 5], [lofa: 7]}]
# }
#######################
quote bind_quoted: [arg: arg] do
def balabab, do: unquote(arg)
end
end
end
defmodule C do
require A
A.inject_map(Macro.escape(%{lofa: 7}))
end
##################################################
### Getting to the Elixir macro that dynamically
### creates multiple functions
##################################################
### try no 1 ###
defmodule SwapMaker do
defmacro make_fixed_swapper(left, right) do
quote bind_quoted: [l: left, r: right] do
def fixed_swapper(unquote(l)), do: unquote(r)
def fixed_swapper(unquote(r)), do: unquote(l)
end
end
def generate_multiple_fixed_swappers(pairs) do
for {left, right} <- pairs do
make_fixed_swapper(left, right)
end
end
end
defmodule TestingGrounds do
require SwapMaker
SwapMaker.make_fixed_swapper(:a,27)
end
# RESULTS:
# wouldn't even compile because
# "** (ArgumentError) cannot invoke def/2 inside function/macro"
### try no 2 ###
defmodule SwapMaker do
defmacro make_fixed_swapper(left, right) do
quote bind_quoted: [l: left, r: right] do
def fixed_swapper(unquote(l)), do: unquote(r)
def fixed_swapper(unquote(r)), do: unquote(l)
end
end
def generate_multiple_fixed_swappers(pairs) do
for {left, right} <- pairs do
quote do
make_fixed_swapper(unquote(left), unquote(right))
end
end
end
end
defmodule TestingGrounds do
require SwapMaker
SwapMaker.make_fixed_swapper(:a,27)
SwapMaker.generate_multiple_fixed_swappers([{:b,7}, {:c, 23}])
end
# RESULTS:
# closer but instead of generating a def it returns the quoted def
### try no 3 ###
defmodule Swapper do
defmodule Helper do
defmacro make_fixed_swapper(left, right) do
quote bind_quoted: [l: left, r: right] do
def fixed_swapper(unquote(l)), do: unquote(r)
def fixed_swapper(unquote(r)), do: unquote(l)
end
end
end
defmodule Maker do
require Helper
defmacro generate_multiple_fixed_swappers(pairs) do
for {left, right} <- pairs do
quote do
Helper.make_fixed_swapper(unquote(left), unquote(right))
end
end
end
end
end
defmodule TestingGrounds do
require Swapper.Maker
require Swapper.Helper
Swapper.Helper.make_fixed_swapper(:a,27)
Swapper.Maker.generate_multiple_fixed_swappers([{:b,7}, {:c, 23}])
end
# RESULTS:
# It's a miracle!
### try no 4 ###
defmodule Swapper do
defmodule Helper do
defmacro make_fixed_swapper(left, right) do
quote bind_quoted: [l: left, r: right] do
def fixed_swapper(unquote(l)), do: unquote(r)
def fixed_swapper(unquote(r)), do: unquote(l)
end
end
end
defmodule Maker do
require Helper
defmacro generate_multiple_fixed_swappers(pairs) do
quote do
for {left, right} <- unquote(pairs) do
Helper.make_fixed_swapper(left, right)
end
end
end
end
end
defmodule TestingGrounds do
require Swapper.Maker
require Swapper.Helper
Swapper.Helper.make_fixed_swapper(:a,27)
list = [{:b,7}, {:c, 23}]
Swapper.Maker.generate_multiple_fixed_swappers(list)
end
# RESULTS:
# It's a miracle!
### try no 5 (exercise is overcomplication) ###
defmodule Swapper do
defmodule Helper do
defmacro make_custom_swapper(name, left, right) do
quote bind_quoted: [n: name, l: left, r: right] do
def unquote(:"#{n}_swapper")(unquote(l)), do: unquote(r)
def unquote(:"#{n}_swapper")(unquote(r)), do: unquote(l)
end
end
end
defmodule Maker do
require Helper
defmacro generate_multiple_custom_swappers(pairs) do
quote do
for {n,l,r} <- unquote(pairs) do
Helper.make_custom_swapper(n,l,r)
end
end
end
end
end
defmodule TestingGrounds do
require Swapper.Maker
require Swapper.Helper
Swapper.Helper.make_custom_swapper(:lofa,:a,27)
letters = String.split("abcdefghijklmnopqrstuvwxyz", "", trim: true)
letter_swapper_list =
for l <- letters do
{:letter, l, String.upcase(l)}
end
things_to_swap = [{:eclipse, "moon", "sun"}|letter_swapper_list]
Swapper.Maker.generate_multiple_custom_swappers(things_to_swap)
end
# RESULTS:
# The cake is not a lie after all.
### try no 6 (exercise is overcomplication) ###
defmodule Swapper do
defmodule Helper do
defmacro make_custom_swapper(name, left, right) do
quote bind_quoted: [n: name, l: left, r: right] do
def unquote(:"#{n}_swapper")(unquote(l)), do: unquote(r)
def unquote(:"#{n}_swapper")(unquote(r)), do: unquote(l)
end
end
end
defmodule Maker do
@after_compile __MODULE__
def __after_compile__(env, _bc) do
require __MODULE__
generate_multiple_custom_swappers(:stuff, 7, 9)
end
require Helper
defmacro generate_multiple_custom_swappers(pairs) do
quote do
for {n,l,r} <- unquote(pairs) do
Helper.make_custom_swapper(n,l,r)
end
end
end
end
end
defmodule TestingGrounds do
require Swapper.Maker
require Swapper.Helper
Swapper.Helper.make_custom_swapper(:lofa,:a,27)
letters = String.split("abcdefghijklmnopqrstuvwxyz", "", trim: true)
letter_swapper_list =
for l <- letters do
{:letter, l, String.upcase(l)}
end
things_to_swap = [{:eclipse, "moon", "sun"}|letter_swapper_list]
Swapper.Maker.generate_multiple_custom_swappers(things_to_swap)
end
# RESULTS:
# Won't compile
# ** (CompileError) iex:15: you are trying to use the module Swapper.Maker which is currently being defined.
### try no 7 (exercise is overcomplication) ###
defmodule Swapper do
defmodule Helper do
defmacro make_custom_swapper(name, left, right) do
quote bind_quoted: [n: name, l: left, r: right] do
def unquote(:"#{n}_swapper")(unquote(l)), do: unquote(r)
def unquote(:"#{n}_swapper")(unquote(r)), do: unquote(l)
end
end
end
defmodule Maker do
require Helper
defmacro generate_multiple_custom_swappers(pairs) do
quote do
for {n,l,r} <- unquote(pairs) do
Helper.make_custom_swapper(n,l,r)
end
end
end
end
require Helper
Helper.make_custom_swapper(:stuff, 7, 9)
end
defmodule TestingGrounds do
require Swapper.Maker
require Swapper.Helper
Swapper.Helper.make_custom_swapper(:lofa,:a,27)
letters = String.split("abcdefghijklmnopqrstuvwxyz", "", trim: true)
letter_swapper_list =
for l <- letters do
{:letter, l, String.upcase(l)}
end
things_to_swap = [{:eclipse, "moon", "sun"}|letter_swapper_list]
Swapper.Maker.generate_multiple_custom_swappers(things_to_swap)
end
# RESULTS:
# Won't compile
# ** (CompileError) iex:22: module Swapper.Helper is not loaded but was defined. This happens when you depend on a module in the same context it is defined.
#######################################################
# saving values into variables in macros and using them
#######################################################
##################################################
### (A) using function from same module than macro
##################################################
defmodule A do
def lofa do
27
end
defmacro vmi do
v = lofa()
quote do
def balabab, do: unquote(v)
end
end
end
defmodule C do
require A
A.vmi
end
C.balabab #>27
##################################################
### (B) using function from same module than macro
### but calling it with a parameter
### (macro and function both)
##################################################
defmodule A do
def lofa(dolog) do
dolog + 27
end
defmacro vmi(arg) do
quote bind_quoted: [arg: arg] do
v = A.lofa(arg)
def balabab, do: unquote(v)
end
end
end
defmodule C do
require A
A.vmi(2)
end
C.balabab #> 29
###########################
# some extras along the way
###########################
# great reads:
# (1) https://thepugautomatic.com/2015/10/understanding-elixir-macros/
# (2) http://stackoverflow.com/questions/34300162/elixir-macro-expansion-problems-but-only-in-a-comprehension
defmodule A do
defmacro lofa(a) do
quote bind_quoted: [portekak: a] do
for x <- portekak do
IO.puts x
end
Enum.each(portekak, fn(x) -> IO.puts(x) end)
end
end
end
defmodule C do
require A
a = [:a, 27]
A.lofa a
end
# RESULTS: (this gets printed immediately)
# 27
# a
# 27
# a
defmodule A do
defmacro lofa(a) do
quote bind_quoted: [porteka: a] do
def vmi(porteka) do
unquote(porteka)
# IO.puts a
end
end
end
end
defmodule C do
require A
a = "11/27/2017"
A.lofa a
end
# RESULTS:
# iex(4)> C.vmi "11/27/2017"
# "11/27/2017"
# iex(5)> C.vmi "11/27/2019"
# "11/27/2017"
defmodule A do
defmacro lofa(a) do
quote bind_quoted: [porteka: a] do
def vmi(unquote(porteka)) do
unquote(porteka)
# IO.puts a
end
end
end
end
defmodule C do
require A
a = "11/27/2017"
A.lofa a
end
# RESULTS
# iex(9)> C.vmi "11/27/2017"
# "11/27/2017"
# iex(10)> C.vmi "11/27/2019"
# ** (FunctionClauseError) no function clause matching in C.vmi/1
# iex:11: C.vmi("11/27/2019")
defmodule A do
defmacro lofa(a) do
quote bind_quoted: [porteka: a] do
def vmi(unquote(porteka)) do
porteka
# IO.puts a
end
end
end
end
defmodule C do
require A
a = "11/27/2017"
A.lofa a
end
# RESULTS
# wouldn't compile ("undefined function porteka/0")
defmodule A do
defmacro lofa(a) do
quote bind_quoted: [porteka: a] do
def vmi(unquote(porteka) = p) do
p
# IO.puts a
end
end
end
end
defmodule C do
require A
alamizsna = "11/27/2017"
A.lofa alamizsna
end
# RESULTS
# iex(9)> C.vmi "11/27/2017"
# "11/27/2017"
# iex(10)> C.vmi "11/27/2019"
# ** (FunctionClauseError) no function clause matching in C.vmi/1
# iex:11: C.vmi("11/27/2019")
defmodule A do
@after_compile __MODULE__
def __after_compile__(_env, _bc), do: lofa
def lofa, do: IO.inspect(27)
end
#> 27 immediately
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment