Think different(ly)
Get out of your rut and learn new ways to think.
Pattern matching works on shape and content.
The Elixir implementation of fibonacci reflects its specification.
Pattern matching can be used to build a parser.
Functions transform data.
Functions compose.
Replace imperative code with a declaritive parse.
Use a pipeline to make the transformations explicit.
Benefits of this style of programming:
- Easily made parallel
- DSL could define business flow
- Granular reuse
- Easier testing
- Better error handling
Please do not give us another Rails.
Elixir is a free restart. Don't reinvent the past. Invent a new future.
Why Elixir?
- Curiosity
- Concurrency
- Maintainability
Many frameworks that claim to be "functional" programming are "faux"nctional programming.
Recursion is useful for more than just job interview questions.
Imperative problem solving: Problem -> Outliers -> Sub-problem
Recursive problem solving: Problem -> Sub-problem -> Outliers
State:
"A specific value for an identity at a point in time."
-Rich Hickey
Elixir is like Ruby
Nope:
- Functional programming
- Pattern matching
- Recursion
- Immutable data structures
Elixir is like CoffeeScript for Erlang.
Nope:
- Better tooling (iex)
- First-class docs
- Meta-programming
- Responsible macros
Actors are more object-oriented than objects
Yep:
- Lock-free concurrency
- What Alan Kay likely wanted
- Processes encapsulate state
- OTP/Supervision trees
Elixir is a language in its own right
Yep
Lists have different semantics than arrays.
Tuples are frequently used in pattern matching.
Maps can't update non-existant keys.
Comprehensions can be used for mapping, filtering, and combining.
Tasks are for executing asynchronous operations.
Lots of microcontroller options:
- Arduino
- BeagleBone Black
- PCDuino
- Raspberry Pi
- A80 Optimus Board
- etc
The Raspberry Pi has all sorts of accessories.
Raspberry Pi only has one hardware PWM pin.
pi-blaster allows for 8 software PWM pins.
Twitter | Slides | Hex website
Hex integrates with Mix, but it is independent. It does this by creating a Mix task.
Mix can create code archives.
Elixir does not guarantee binary compatibility between versions.
Hex uses Plug and Ecto.
Plug doesn't handle HTTP caching, authentication, and media type handling.
Hex serialization uses Erlang terms instead of JSON to avoid a JSON dependency. However, this means new versions of Elixir can break reading old serializations.
Elixir may be good at large-scale, fault-tolerant applictations, but it has more to offer.
Elixir has a great standard library.
Umbrella projects can be used to run a task across several apps.
Mix is similar to Rake.
Mix tasks just use the Mix behavior and define a run function.
Make an escript => mix escript.build
escripts just need the Erlang runtime in order to run.
Elixir comes with OptionParser for parsing command line arguments.
Elixir has great testing tools.
IO.ANSI
can manipulate the terminal colors and other great features.
Port
can be used to run shell commands.
ExUnit.CaptureIO
can be used to test CLI IO.
Agent
creates a simple abstraction around state.
Rotor can watch file paths.
OTP is basically a microservices tool.
Actors are the overlap between OO, FP, and concurrency.
Behaviors: Don't call us, we'll call you.
defoverridable
allows for functions to be overriden. This is great for default implementations of methods in a Behaviour.
Modules are not classes, they are namespaces.
GenEvent
is great for making things like loggers.
Supervisors are the heart of "let it fail."
Supervision trees are also great because they are so modular. The structure of the tree can be easily copied/rearranged/etc.
The Intel MPI Benchmarks test performance of communication with different sized messages.
Erlang and Elixir out perform the standard languages, including Scala.
Erlang, Elixir, and ooErlang are limited only by the host machine and available resources.
Scala, Java, Python, and Ruby were unable to complete all the tests. They also didn't use all the machine's resources.
MPI measures communication, not granularity of languages.
Ported a telecom system from C to Elixir.
Result: Less capacity but more modularity, scalability, and extensibility.
Macros make programming in Elixir so much better than C.
Exrm can be used to create releases. It packages up the application with the Erlang and Elixir run time.
Create RPM from Exrm with exrm-rpm
Benefits of Elixir:
- Supervisors
- Call processing error drops 1 phone, not 1000s.
- Live upgrade
- Powerful remote console.
- Multi-node scalability.
- Easier to maintain (less code, no semaphores, no pointer corruption, etc).
Challenges:
- Had to train devs in Elixir.
- Team buy-in, effort to port and performance concerns.
- Defining the architecture while learning Elixir/FP.
- Elixir language churn:
- Porting 25+ records with OO modeled customer functions.
- Immature eco system (syslog, Sun RPC, etc).
- Choose test framework not actively being maintained.
Module.register_attribute/3 can expose attributes to all modules.
The Free Lunch is Over -Herb Sutter
Creating Elixir was really hard. Implementing OO principles didn't work. Embracing FP was important. Compatibility with Erlang is important.
Lego lang was the basis for Elixir.
Everything is a function, which can be converted into a LISP-like format. Macros can be performed on the LISP-like format.
Macros must be explicitly required. This prevents injecting macros globally that can cause interference in other parts of our code.
Clojure is the biggest inspiration behind Elixir.
Elixir goals:
- Productivity
- First class docs
- Tooling (Mix, ExUnit, IEx)
- Hex Packages
- Extensibility
- Macros
- Structs and protocols (polymorphism)
- Compatibility
- Concurrency
- Distribution
- Embrace and extend
concuerror systematically tests concurrent Erlang programs.
Prowess does property-based testing.
Haskell has comprehensive comprehensions, Common LISP has the LOOP macro and do+ pkg.
Using macros, Elixir could add a stream for
or parallel for
comprehension. Parallel options: Unbound vs. Pool, Pipelines, Feedback.
It was never written down why Erlang was implemented how it was. This information is useful for people who come along later on.
Concurrency != parallelism. Concurrency is a feature of the language or problem. Parallelism is what the underlying system provides.
The C10k problem was trivial for us.
We weren't trying to make an FP language or implement the actor model. We were trying to solve the problem. The language evolved to solve the problem.
First principles:
- Lightweight concurrency
- Async communication
- Process isolation
- Error handling
- Continuous evolution of the system.
- High level
- Simple
- Provide tools for building systems, not solutions.
Erlang has no user-defined data types. This is by design.
An ETS table is a mutable data structure, but the data in it is immutable.
All processes are equal. No special or system processes. No process hierarchy–flat process space.
Everything uses processes. All communication are by asynchronous messages, no backdoors. One exception is sending to registered names. It works synchronously.
Ports obey process semantics.
Ports talk to hardware.
Errors will always get errors no matter how good you are.
Parts may crash and burn, but the system must never go down.
The system must be able to detect, contain, handle, and recover from errors.
Robust systems must always be aware of errors, but we don't want to write error handling code everywhere.
Module is both the unit of compilation and of all code handling.
No inter-module dependencies.
All modules are equal—no system or special modules.
No module hierarchy—flat module space. This causes problems with name conflicts. Elixir solves this with prefixing module names.
Things missing in early Erlang:
- Code handling (very very early...depended on Prolog)
- Binaries
- ETS
- Funs
- OTP
- NIFs
"Component" is a better name than "appliction" in Erlang.
Systems built with Erlang tend to be very OS like. Provide services. Very seldom a central thread of execution.
Guards added to provide simple tests for extending pattern matching.
The problem domain of Erlang doesn't just apply to telecoms. Which is why is provides a solution to many problems today.
Elixir's syntax may look similar to Ruby, but it stops there.
The concurrency model is where you really need to change your thinking, more than the syntax.
Engineers never understand a problem until they've solved it, and once they've solved it they'll know a million better ways.
Create a process for every concurrent action.
Sharing nothing and having many processes isolates errors.
Bottlenecks will hinder throughput and scaling.
Erlang on Xen fires up virtual machines and has them communicate through software-defined networking.
Elixir balances practicality and power.
Macros: With great power comes great responsibility. -Uncle Ben
@external_resource
Specify an external resource to the current module. Many times a module embeds information from an external file. This attribute allows the module to annotate which external resources have been used. Tools like Mix may use this information to ensure the module is recompiled in case any of the external resources change.
The upcoming Logger
, makes error messages much cleaner.
IEx.pry
can stop the interpreter and allows us to inspect the state of the program.
Fear is a driving factor. It can be motivating or paralyzing.
Languages have varying adoption curves.
Language paradigms also have adoption curves: HLL -> Structure -> OOP -> FP
Seven Languages | Seven More Languages
10 diskettes to deploy C++.
What are the paralyzing fears, and how are they getting smaller? What are the motivating fears, and how are they growing?
Paralyzing fears reducing:
- Building communities is easy.
- OO languages, FP features.
- Deployment options abound.
- Interfaces between programs are cleaner.
Motivating fears increasing:
- Code complexity (always first)
- Multicore and Distribution
- Complexity
Concurrency is hard when the burden is on the developer.
Sometimes the business case is better than the technical case when trying to adopt a language.
Lots of supervision trees and state machines.
Game loops can be implemented as state machines.
The real world isn't always easy to work with (ex. connections lost, upgrade servers, etc).
When upgrading a server:
- The players in the lounge get moved to a new server.
- Users that lose connection and reconnect are moved to the new server.
- When a game ends, the players are moved to the new server.
- When all players are off the server, then the server is upgraded.
Elixir doesn't wrap :ets
or :gen_fsm
yet.
alias :ets, as: ETS
Phoenix is an MVC web framework.
Main features;
- Robust routing
- WebSocket/PubSub layer (client and server)
- Plug-base Routers and controllers
- Precompiled view layer.
"What if I could just..." driven development -> not always good.
"Web" Framework Goals:
- "Distributed web services" framework
- Batteries included.
- Shared conventions
- Common tasks should be easy.
- WebSockets
- Realtime events
- Service-Oriented Architecture (SOA)
- No productivity sacrifices for performance.
- No performance sacrifices for productivity.
Phoenix uses macros and pattern matching for routing. This makes easy, yet performant routing.
Views render templates. Views are the presentation layer. EEx and Haml engine support.
Views are pre-rendered. This creates a bunch of functions from the views. All that happens at compile time. This just leaves some string concatenation at run time.
Phoenix uses "channels" on top of WebSockets.
Future plans include distributed services, inspired by riak_core.
It's hard to show great examples of what Elixir/Erlang are good at because they are big systems. That's not something that can be seen in a small blog post.
It's great that Elixir/Erlang is good at concurrency, but how is it going to help my day job?
When you are successful with Elixir/Erlang, tell the world about it. Many of the big companies don't say much about their successes.
Don't copy Rails because it's what you're used to or because it's popular. Implement something because it's the best way to do so.
Macros aren't about code injecting/generating code. Macros are about code transforming code.
When you see Elixir application, think "component."
Don't wrap Erlang code unless there is a really good reason. Just use it.
We don't need a bunch of boring JSON libraries.