2015-08-03
Luke Imhoff
| luke_imhoff@rapid7.com | Kronic.Deth@gmail.com | |
| @limhoff-r7 | @KronicDeth | |
| @KronicDeth |
| Ruby | Elixir | ||
|---|---|---|---|
| Paradigms | Imperative | ✓ | |
| Concurrent | ✓ | ||
| Functional | ✓ | ✓ | |
| Object-Oriented | ✓ | ||
| Typing | Dynamic | ✓ | ✓ |
| Duck | ✓ | ||
| Strong | ✓ | ||
| Mutability | Mutable | Immutable | |
| Concurrency | CPU-bound | OS Processes | VM Processes |
| IO-bound | Threads, Fibers | VM Processes | |
| Metaprogramming | Runtime, Class Methods | Compilation, Macros | |
| Ruby | Elixir | |
|---|---|---|
| Homebrew |
brew install ruby
|
brew install elixir
|
| Version Manager |
rvm install VERSION
|
kiex install VERSION
|
| Ruby | Elixir | |
|---|---|---|
| Installer |
rubyinstaller.exe
|
elixir-websetup.exe
|
| Chocolatey |
cinst ruby
|
cinst elixir
|
Use your package manager
| Ruby | Elixir |
|---|---|
irb
|
iex
|
| Ruby | Elixir |
|---|---|
| CTRL+C | #iex:break on line by itself |
| Ruby | Elixir |
|---|---|
exit |
CTRL+C CTRL+C |
| Ruby | Elixir | ||
|---|---|---|---|
| Name | Example | Name | Example |
| Integer |
9, 0b1, 0o7, 0xF
|
Integer |
9, 0b1, 0o7, 0xF
|
| Float |
1.2, 3e+0
|
float |
1.2, 3e+0
|
| Ruby | Elixir | ||
|---|---|---|---|
| Name | Example | Name | Example |
| Symbol |
:symbol, :"symbol", :'symbol'
|
Atom |
:atom, :"atom", :'atom'
|
| Class/Module name |
MyClass, MyNamespace::MyModule
|
Alias |
MyModule, MyNamespace.MyModule, :erlang_module
|
| Constant |
MY_CONSTANT
|
Module Attribute |
@my_attribute
|
| Ruby | Elixir |
|---|---|
false
|
false, :false
|
nil
|
nil, :nil
|
true
|
true, :true
|
| Ruby | Elixir | |||
|---|---|---|---|---|
| Format |
"string"
|
"string"
|
||
| Interpolation |
"Hello #{:world}"
|
✓ |
"Hello #{:world}"
|
✓ |
| Encoding | UTF-8 | UTF-8 | ||
| Unicode Capitalization |
"José Valim".upcase # "JOSé VALIM"
|
❌ |
String.upcase "José Valim" # "JOSÉ VALIM"
|
✓ |
| Unicode Graphemes Rendering |
"\u0065\u0301" # "é"
|
✓ |
"\x{0065}\x{0301}" # "é"
|
✓ |
| Unicode Graphemes Length |
"\u0065\u0301".length # 2
|
❌ |
String.length "\x{0065}\x{0301}" # 1
|
✓ |
| Ruby | Elixir | |
|---|---|---|
| Literals |
|
|
| Compile |
Regexp.new "string"
|
Regexp.compile! "string"
|
| Replace |
|
|
| Ruby | Elixir | |
|---|---|---|
| Declaration |
|
|
| Calling |
|
|
| Ruby | Elixir | ||
|---|---|---|---|
| Name | Example | Name | Example |
| Array |
[1,2,3]
|
Tuple |
[1,2,3]
|
| Hash |
|
Map |
|
| Set |
Set.new [1,2,3]
|
HashSet |
Enum.into [1,2,3], HashSet.new
|
| ❌ | ❌ | Linked List |
|
| ❌ | ❌ | Keyword List |
|
| Ruby | Elixir |
|---|---|
|
|
|
|
|
|
| Ruby | Elixir |
|---|---|
|
|
begin
|
try do
|
rescue Klass => instance
|
variable in [Alias] ->
|
rescue Klass
|
Alias ->
|
rescue => exception
|
error ->
|
rescue
|
_ ->
|
else
|
else
|
ensure
|
after
|
end
|
end
|
| Ruby | Elixir |
|---|---|
|
|
| Step | Ruby | Elixir | |
|---|---|---|---|
| 1 |
foo = 1
|
foo is 1
|
foo is 1
|
| 2 |
1 = foo
|
SyntaxError |
foo is 1
|
| 3 |
2 = foo
|
SyntaxError | ** (MatchError) no match of right hand side value: 1 |
| Ruby | Elixir | ||
|---|---|---|---|
| Expression | Variable Value(s) | Expression | Variable Value(s) |
a, b = [1, 2]
|
a = 1b = 2
|
{a, b} = {1, 2}
|
a = 1b = 2
|
_, b = [1, 2]
|
b = 2
|
{_, b} = {1, 2}
|
b = 2
|
a, b* = [1, 2, 3]
|
a = 1b = [2, 3]
|
[a | b] = [1, 2, 3]
|
a = 1b = [2, 3]
|
| Ruby | Elixir | ||
|---|---|---|---|
| Expression | Variable Value(s) | Expression | Variable Value(s) |
|
ArgumentError: a should be 1 |
|
** (MatchError) no match of right hand side value: {nil, 2} |
|
ArgumentError: opening and closing tag don't match |
|
* (MatchError) no match of right hand side value: {:td, :th} |
| Ruby | Elixir |
|---|---|
|
|
| Ruby | Elixir |
|
|
|---|
| Ruby | Elixir | Ruby | Elixir |
|---|---|---|---|
gem list
|
mix archive
|
bundle help
|
mix help
|
gem build *.gemspec
|
mix archive.build
|
gem help
|
mix help
|
gem install *.gem
|
mix archive.install
|
rake -T
|
mix help
|
gem uninstall NAME
|
mix archive.uninstall NAME
|
bundle outdated
|
mix hex.outdated
|
rm *.gem
|
mix clean
|
gem owner
|
mix hex.owner
|
bundle list
|
mix deps
|
gem push
|
mix hex.publish
|
bundle install
|
mix deps.get
|
gem query
|
mix hex.search
|
rm Gemfile.lock
|
mix deps.unlock --all
|
bundle gem
|
mix new
|
bundle update
|
mix deps.update --all
|
rake spec
|
mix test
|
rake TASK1 TASK2
|
mix do TASK1 TASK2
|
| Ruby | Elixir |
|---|---|
gem install bundler
|
|
bundle gem example --coc --mit --test=rspec
|
mix new example
|
| Ruby | Elixir |
|---|---|
example.gemspec
|
mix.exs
|
.gitignore
|
.gitignore
|
Gemfile
|
mix.exs
|
lib/example.rb
|
lib/example.ex
|
lib/example/version.rb
|
mix.exs
|
README.md
|
README.md
|
spec/spec_helper.rb
|
test/test_helper.exs
|
spec/example_spec.rb
|
test/example_test.exs
|
| Ruby | Elixir |
|---|---|
|
|
| Source | Ruby | Elixir | |
|---|---|---|---|
*.gemspec
|
Gemfile
|
mix.exs
|
|
| Packager |
spec.add_runtime_dependency 'mydep', '~> 1.2.3'
|
gem 'mydep', '~> 1.2.3'
|
{:mydep, "~> 0.3.0"}*
|
| Github | ❌ |
gem 'mydep', github: 'myorg/mydep', tag: 'v1.2.3'
|
{:mydep, github: 'myorg/mydep', tag: "v1.2.3"}
|
| Path | ❌ |
gem 'mydep', path: 'path/to/mydep'
|
{:mydep, path: "path/to/mydep"
|
*All mix dependencies are added to the [] in
Example.MixFile.deps/0
| Environment | Ruby | Elixir | |
|---|---|---|---|
*.gemspec
|
Gemfile
|
mix.exs
|
|
| development |
spec.add_development_dependency 'mydep', '~> 1.2.3'
|
gem 'mydep', '~> 1.2.3', group: :development
|
{:mydep, '~> 1.2.3', only: :dev}
|
| test | &10060; |
gem 'mydep', '~> 1.2.3', group: :test
|
{:mydep, '~> 1.2.3', only: :test}
|
*All mix dependencies are added to the [] in
Example.MixFile.deps/0
| Name | Ruby | Elixir |
|---|---|---|
| Optional |
group optional: true
|
{:mydep, '1.2.3', optional: true}
|
| Override | ❌ |
{:mydep, '1.2.3', override: true}
|
| Ruby | Elixir |
|---|---|
|
|
| Ruby | Elixir |
|---|---|
|
|
| Ruby | Elixir | |
|---|---|---|
|
|
|
defmodule Rectangle do
defstruct [:height, :width]
def area(%__MODULE__{height: height, width: width}) do
height * width
end
end
defmodule Circle do
defstruct [:radius]
def area(%__MODULE__{radius: radius}) do
:math.pi * radius * radius
end
end
rectangle = %Rectangle{height: 3, width: 2}
Rectangle.area(rectangle) # 6
circle = %Circle{radius: 3}
Circle.area(circle) # 28.274333882308138
Circle.area(rectangle) # ** (FunctionClauseError) no function clause matching in Circle.area/1
| Protocol | Implementation | |
|---|---|---|
| Inside | Outside | |
|
|
|
rectangle = %Rectangle{height: 3, width: 2}
circle = %Circle{radius: 3}
Shape.area(rectangle) # 6
Shape.area(circle) # 28.274333882308138
| Ruby | Elixir |
|---|---|
|
|
| Ruby | Elixir | Usage |
|---|---|---|
| minitest/unit | ex_unit | assertion based unit testing |
| shouldi | Nested contexts for ex_unit | |
| faker | faker | Fake data for tests |
| rspec | espec | BDD tests with expect, let, callbacks |
| cucumber | white_bread | Story-based BDD using gherkin syntax |
| Ruby | Elixir |
|---|---|
| spec/spec_helper.rb | test/test_helper.exs |
|
|
| Ruby | Elixir |
|---|---|
| spec/example_spec.rb | test/example_test.exs |
|
|
| Ruby | Elixir |
|---|---|
rake spec
|
mix test
|
|
|
| Type | Ruby | Elixir |
|---|---|---|
| Built-in | (no builtin reporting from Coverage library) |
|
| Coverage report with ignores |
|
|
|
Upload coverage reports to Coveralls.io from Travis-CI.org |
|
|
| Code | Commands |
|---|---|
|
|
| Ruby | Elixir | ||
|---|---|---|---|
and
|
Keyword |
and
|
Macro |
def
|
Keyword |
def
|
Macro |
if
|
Keyword |
if
|
Macro |
in
|
Keyword |
in
|
Macro |
module
|
Keyword |
defmodule
|
Macro |
not
|
Keyword |
not
|
Function |
or
|
Keyword |
or
|
Macro |
self
|
Keyword |
self
|
Function |
unless
|
Keyword |
unless
|
Macro |
| Block | Keyword List |
|---|---|
|
|
defmodule ExampleTest do
use ExUnit.Case
test "the truth" do
assert 1 + 1 != 2
end
end
defmodule MimeTypes do
HTTPotion.start
HTTPotion.Response[body: body] = HTTPotion.get(
"http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types"
)
Enum.each String.split(body, %r/\n/), fn (line) ->
unless line == "" or line =~ %r/^#/ do
[ mimetype | _exts ] = String.split(line)
def is_valid?(unquote(mimetype)), do: true
end
end
def is_valid?(_mimetype), do: false
end
MimeTypes.is_valid?("application/vnd.exn") #=> false
MimeTypes.is_valid?("application/json") #=> true
# deeply nested function calls
def main(argv) do
output(process(parse_args(argv)))
end
# single-use variables
def main(argv) do
parsed_args = parse_args(argv)
processed = process(parsed_args)
output(processed)
end
# Pipes
def main(argv) do
argv
|> parse_args
|> process
|> output
end
defmacro left |> right do
[{h, _}|t] = Macro.unpipe({:|>, [], [left, right]})
:lists.foldl fn {x, pos}, acc -> Macro.pipe(acc, x, pos) end, h, t
end
iex> pid = spawn_link fn ->
...> receive do
...> {:ping, client} -> send client, :pong
...> end
...> end
#PID<9014.59.0>
iex> send pid, {:ping, self}
{:ping, #PID<0.73.0>}
iex> flush
:pong
:ok
f = fn -> receive do
after
:infinity -> :ok
end
end
{_, bytes} = Process.info(spawn(f), :memory)
bytes # 2680
defmodule ElixirLunchAndLearn.Chain do
def run(n) do
{microseconds, result} = :timer.tc(__MODULE__, :create_processes, [n])
IO.puts "#{result} (Calculated in #{microseconds} microseconds)"
end
end
defmodule ElixirLunchAndLearn.Chain do
def create_processes(n) do
last = Enum.reduce 1..n,
self,
fn(_, send_to) ->
spawn(__MODULE__, :counter, [send_to])
end
# start the count by sending
send last, 0
# and wait for the result to come back to us
receive do
final_answer when is_integer(final_answer) ->
"Result is #{inspect final_answer}"
end
end
end
defmodule ElixirLunchAndLearn.Chain do
def counter(next_pid) do
receive do
n ->
send next_pid, n + 1
end
end
end
> elixir --erl "+P 1000000" -r lib/elixir_lunch_and_learn/chain.ex -e "ElixirLunchAndLearn.Chain.run(1_000_000)"
Result is 1000000 (Calculated in 11658944 microseconds)
| Ruby | Elixir | |
|---|---|---|
| Official Intro | Getting Started | |
| How I Start | Gem to referring to its own article | Portal |
| Learn X in Y minutes | Where X=ruby | Where X=elixir |
| Ruby | Elixir | |
|---|---|---|
| IRC (Freenode) | #ruby-lang | #elixir-lang |
| Google Group | comp.lang.ruby | elixir-lang-talk |
| Meetups | Austin.RB | Austin Elixir |
| Ruby | Elixir | |
|---|---|---|
| Screencasts | rubytapas.com | elixirsips.com |
| Conference Talks | Confreaks on Youtube |
| Book | Topics |
|---|---|
| Programming Elixir | Sequential Elixir, Concurrent Elixir, Intro to Macros |
| Elixir In Action | Go from a simple, sequential command-line todo app to a concurrent, fault-tolerant todo web service. |
| Metaprogramming Elixir | Advanced Macros |
| Ruby | Elixir | |
|---|---|---|
| Official | ruby-lang.org | elixir-lang.org |
| Projects by use | Awesome Ruby | Awesome Elixir |
| Ruby | Elixir | |
|---|---|---|
| Emacs | ? | Alchemist |
| Jetbrains | Rubymine | IntelliJ Elixir |
| Vim | vim-ruby | vim-elixir |
| Start | End | Event | Language |
|---|---|---|---|
| 2015-08-05 | 2015-08-05 | Elixir Austin | Elixir |
| 2015-08-13 | 2015-08-14 | Phoenix Framework Training | Elixir |
| 2015-08-15 | 2015-08-15 | Lone Star Ruby | Ruby |
| 2015-10-01 | 2015-10-03 | Elixir Conf | Elixir |
| 2015-10-23 | 2015-10-23 | Keep Ruby Weird | Ruby |
| 2015-11-15 | 2015-11-17 | Ruby Conf | Ruby |