Last active
March 20, 2017 03:48
-
-
Save toraritte/7b617d4b4e1614f582037a252bc6bde1 to your computer and use it in GitHub Desktop.
Saving different working use cases using Elixir metaprogramming
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################################### | |
### 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