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 = 1 b = 2
|
{a, b} = {1, 2}
|
a = 1 b = 2
|
_, b = [1, 2]
|
b = 2
|
{_, b} = {1, 2}
|
b = 2
|
a, b* = [1, 2, 3]
|
a = 1 b = [2, 3]
|
[a | b] = [1, 2, 3]
|
a = 1 b = [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 |