Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
Elixir in Elixir
Jay Hayes
March 15, 2018
Programming
1
130
Elixir in Elixir
https://www.codesync.global/speaker/jay-hayes/
Jay Hayes
March 15, 2018
Tweet
Share
More Decks by Jay Hayes
See All by Jay Hayes
Build Elixir Phoenix
iamvery
0
50
A Less Complex Web with Ratchet & Jank
iamvery
0
100
Feature. Tests. Implementation.
iamvery
0
52
Ratchet & Jank
iamvery
0
160
Elixir in Elixir
iamvery
6
930
Rubyist Does Swift
iamvery
0
51
Swift Introduction
iamvery
0
110
Data Integrity
iamvery
0
69
Pairing with tmux
iamvery
2
140
Other Decks in Programming
See All in Programming
企業内スモールデータでのデータ解析
hamage9
0
920
Google I/O 2022 Android関連概要 / Google I/O 2022 Android summary
phicdy
1
420
「困りごと」から始める個人開発
ikumatadokoro
4
270
クラウド KMS の活用 / TOKYO BLOCKCHAIN TECH MEETUP 2022
odanado
PRO
0
200
Introduction to Property-Based Testing @ COSCUP 2022
cybai
1
150
回帰分析ではlm()ではなくestimatr::lm_robust()を使おう / TokyoR100
dropout009
0
4.6k
YATA: collaborative documents and how to make them fast
horusiath
1
170
SwiftUI+TCAに挑戦!NewsPicks iOSアプリのリアーキテクチャ/re-architecture-newspicks-ios-app-with-swiftui-and-tca
takehilo
0
420
閱讀原始碼 - 再戰十年的 jQuery
eddie
1
310
Another 40 years of Commodore 64
mehowte
0
110
ちょっとつよい足トラ
logilabo
0
420
それ全部エラーメッセージに書いてあるよ!〜独学でPHPプログラミングが上達するたった一つの方法〜
77web
1
160
Featured
See All Featured
Music & Morning Musume
bryan
35
4.3k
KATA
mclloyd
7
8.9k
Typedesign – Prime Four
hannesfritz
34
1.4k
Intergalactic Javascript Robots from Outer Space
tanoku
260
25k
Rails Girls Zürich Keynote
gr2m
87
12k
The Invisible Side of Design
smashingmag
290
48k
Why You Should Never Use an ORM
jnunemaker
PRO
47
7.7k
Teambox: Starting and Learning
jrom
123
7.7k
Mobile First: as difficult as doing things right
swwweet
213
7.6k
GitHub's CSS Performance
jonrohan
1020
420k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
29
4.4k
Designing on Purpose - Digital PM Summit 2013
jponch
106
5.7k
Transcript
Metaprogramming Programs that Write Programs
InElixir Elixir
ElixirConf Orlando!
How to give the talk
1. Set up the Topic
How much?
It depends™
What is “Elixir”?
Basics + STDLIB
of Elixir is written in Elixir 78%
of Elixir is written in Elixir 78% 87%
Compare to Ruby’s 44%
Numbers? Meh
if
def
|>
Metaprogramming
2. Introductions
https://iamvery.com — @iamvery Jay Hayes
None
None
None
We’re hiring!
3. Definitions
Metaprogramming
None
None
Code generation
Definition, plz
Metaprogramming
programming
Work on Data
4 + 2
4 + 2 data
4 + 2 data func
+(4, 2)
Kernel. ( , ) 4 2 +
4 2 +
4 2 + # => 6
Meta…
The program is data
Programming the Program
Code generation
4. Macros & Expressions
Macros
Macros are everywhere
good macros are invisible
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end macros
What is the data?
What is the data? Program
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
defmodule ItTest do use ExUnit.Case test “works” do assert It.works?
end end
Program macro Program ✨ ✨
Program macro Program ✨ ✨
Program macro Program ✨ ✨
Program macro Program Expression Expression ✨ ✨
Quoted expression?
Code Expressed as Data
{:+, [], [1, 2]}
Abstract Syntax Tree
/ 1 + 2 3
/ 1 + 2 3 / 1 + 2 3
( ( )) / 1 + 2 3
( ( )) / 1 + 2 3 func
( ( )) / 1 + 2 3 func Args
( ( )) / 1 + 2 3 func Args
1 / ( 2 + 3 )
/ 1 + 2 3
{: , …, [ , {: , …, [ ,
]} / 1 + 2 3
{func, meta, args}
{func, meta, args} +(1, 2)
{:+, [], [1, 2]}
Back to macros…
Macros are Functions
Args are quoted
Args are quoted
(not evaluated)
Expression.match?(:lol > :wat) # => false Expression.match?(:lol = :wat) #
=> true
defmacro match?({:=, _, _}) do true end defmacro match?(_expr) do
false end
Compile time
Expression.match?(:lol > :wat) Expression.match?(:lol = :wat)
false true
Must return quoted
Must return quoted
Code generation
defmacro badmatch do {:=, [], [:lol, :wat]} end
Expression.badmatch # (MatchError)
:lol = :wat # (MatchError)
Program macro Program Expression Expression ✨ ✨
5. Quoting
{:+, [], [1, {:-, [], [2, 3]}]}
Quote/2
Easier to grok
Return AST of Expression
Return AST of Expression
{func, meta, args}
quote do :lol = :wat end
{:=, [], [:lol, :wat]}
quote do 1 / (2 + 3) end
{:/, [], [1, {:+, [2, 3]}
{:/, [], [1, {:+, [2, 3]}
]} {:/, [], [1, {:+, [2, 3]}
Why “quote”?
Like strings…
Sort of…
“1+1”
eval “1+1”
eval “1+1” # => 2
quote(do: 1+1)
expr = quote(do: 1+1) Code.eval_quoted(expr)
expr = quote(do: 1+1) Code.eval_quoted(expr) # => {2, []}
Bonus:Syntax✔
Recursive
Terminal Values
Quoted Literals
quote do: “foo” # “foo” quote do: :foo # :foo
quote do: 1 # 1 quote do: [1] # [1] quote do: {1,2} # {1,2} quote do: [a: 1] # [a: 1]
6. Unquoting
Value Injection
Unquote/1
Strings?
Like string interpolation
Sort of…
Inject value into quoted string
name = “Sue” “hi name” # => “hi name”
name = “Sue” “hi #{name}” # => “hi Sue”
Inject value into quoted Expression
num = 42 expr = quote do: 1 + num
# => {:+, [], [1, {:num, [], :Elixir}]}
num = 42 expr = quote do: 1 + num
# => {:+, [], [1, {:num, [], :Elixir}]} Code.eval_quoted(expr) # ERROR! `num` is undefined
num = 42 expr = quote do: 1 + unquote(num)
# => {:+, [], [1, 42]}
num = 42 expr = quote do: 1 + unquote(num)
# => {:+, [], [1, 42]} Code.eval_quoted(expr) # => {43, []}
num = 42 expr = “1 + #{num}” # =>
“1 + 42” eval expr # => 43
Inject value into quoted Expression
“It’s already quoted”
Expression Injection
one = quote do: 1 + 1 # => {:+,
[], [1, 1]} two = quote do: 2 + 2 # => {:+, [], [2, 2]} sum = quote do: unquote(one) + unquote(two) # => {:+, [], [ # {:+, [], [1, 1]}, # {:+, [], [2, 2]} # ]}
Code.eval_quoted(sum) # => {6, []}
7. Write a Macro
if
if foo > 0 do IO.puts(“Pass!”) else IO.puts(“Fail.”) end
if(…, do: …, else: …)
if(…, [do: …, else: …])
if(…, [do: …, else: …]) cond
if(…, [do: …, else: …]) cases cond
Truthy & Falsey
false || nil Falsey
Anything else Truthy
case … do x when x in [false, nil] ->
… _ -> … end
case … do x when x in [false, nil] ->
… _ -> … end cond
case … do x when x in [false, nil] ->
… _ -> … end cond cases
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cond
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cases
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cond
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end cases
defmacro if(c, do: tc, else: fc) do quote do case
unquote(c) do x when x in [false, nil] -> unquote(fc) _ -> unquote(tc) end end end
if do else end IO.puts(“Pass!”) IO.puts(“Fail.”) foo > 0
case do x when x in [false, nil] -> _
-> end IO.puts(“Pass!”) IO.puts(“Fail.”) foo > 0
8. Wrap Up with Guidance
When to macro…
#1 Don’t
Write functions
import String defmacro adds(a, b) do quote do to_integer(unquote(a)) +
to_integer(unquote(b)) end end
import String defmacro adds(a, b) do quote do to_integer(unquote(a)) +
to_integer(unquote(b)) end end
import String def adds(a, b) do to_integer(a) + to_integer(b) end
Prefer direct solutions
Decision based on args
Transformation
Less error-prone
#2 Constants
defmodule Conversions do defmacro seconds_per_minute, do: 60 defmacro minutes_per_hour, do:
60 end
h/t module attrs @second_per_minute
#3 DSL
html do body do div class: “content” do p “Engaging
content.” end end end
scope “/”, HelloWeb do pipe_through :browser get “/”, PageController, :index
end
#4 Hide details
test “works” do assert It.works? end
#5 Reduce boilerplate
Language extension
defmodule Lolwat do def lol do :wat end end
:elixir_module.compile(Lolwat, {:def, [context: Elixir, import: Kernel], [{:lol, [context: Elixir], Elixir},
[do: :wat]]}, [], __ENV__)
:elixir_module.compile(Lolwat, {:def, [context: Elixir, import: Kernel], [{:lol, [context: Elixir], Elixir},
[do: :wat]]}, [], %{__struct__: Macro.Env, aliases: [], context: nil, context_modules: [Mex], export_vars: nil, file: "iex", function: nil, functions: [Mex: [expand_all: 2], "IEx.Helpers": [c: 1, c: 2, cd: 1, clear: 0, flush: 0, h: 0, i: 1, l: 1, ls: 0, ls: 1, nl: 1, nl: 2, pid: 1, pid: 3, pwd: 0, r: 1, recompile: 0, respawn: 0, v: 0, v: 1], Kernel: [!=: 2, !==: 2, *: 2, +: 1, +: 2, ++: 2, -: 1, -: 2, --: 2, /: 2, <: 2, <=: 2, ==: 2, ===: 2, =~: 2, >: 2, >=: 2, abs: 1, apply: 2, apply: 3, binary_part: 3, bit_size: 1, byte_size: 1, div: 2, elem: 2, exit: 1, function_exported?: 3, get_and_update_in: 3, get_in: 2, hd: 1, inspect: 1, inspect: 2, is_atom: 1, is_binary: 1, is_bitstring: 1, is_boolean: 1, is_float: 1, is_function: 1, is_function: 2, is_integer: 1, is_list: 1, is_map: 1, is_number: 1, is_pid: 1, is_port: 1, is_reference: 1, is_tuple: 1, length: 1, macro_exported?: 3, make_ref: 0, map_size: 1, max: 2, min: 2, node: 0, node: 1, not: 1, pop_in: 2, put_elem: 3, put_in: 3, rem: 2, round: 1, self: 0, send: 2, spawn: 1, spawn: 3, spawn_link: 1, spawn_link: 3, spawn_monitor: 1, spawn_monitor: 3, struct: 1, struct: 2, struct!: 1, struct!: 2, throw: 1, tl: 1, trunc: 1, tuple_size: 1, update_in: 3]], lexical_tracker: nil, line: 2, macro_aliases: [], macros: [Mex: [mex: 1], "IEx.Helpers": [b: 1, h: 1, import_file: 1, import_file: 2, import_file_if_available: 1, import_if_available: 1, import_if_available: 2, s: 1, t: 1], Kernel: [!: 1, &&: 2, ..: 2, <>: 2, @: 1, alias!: 1, and: 2, binding: 0, binding: 1, def: 1, def: 2, defdelegate: 2, defexception: 1, defimpl: 2, defimpl: 3, defmacro: 1, defmacro: 2, defmacrop: 1, defmacrop: 2, defmodule: 2, defoverridable: 1, defp: 1, defp: 2, defprotocol: 2, defstruct: 1, destructure: 2, get_and_update_in: 2, if: 2, in: 2, is_nil: 1, match?: 2, or: 2, pop_in: 1, put_in: 2, raise: 1, raise: 2, reraise: 2, reraise: 3, sigil_C: 2, sigil_D: 2, sigil_N: 2, sigil_R: 2, sigil_S: 2, sigil_T: 2, sigil_W: 2, sigil_c: 2, sigil_r: 2, sigil_s: 2, sigil_w: 2, to_char_list: 1, to_charlist: 1, to_string: 1, unless: 2, update_in: 2, use: 1, use: 2, var!: 1, var!: 2, |>: 2, ||: 2]], module: nil, requires: [IEx.Helpers, Kernel, Kernel.Typespec, Mex], vars: []})
None
None
9. Self Promotion
bit.ly/ElixirMeta2
Questions? @iamvery
With love,