Elixir - Maps
Maps
Whenever you need a key-value store, maps are the “go to” data structure in Elixir. A map is created using the %{}
syntax:
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
Compared to keyword lists, we can already see two differences:
- Maps allow any value as a key.
- Maps’ keys do not follow any ordering.
In contrast to keyword lists, maps are very useful with pattern matching. When a map is used in a pattern, it will always match on a subset of the given value:
iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
As shown above, a map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.
Variables can be used when accessing, matching and adding map keys:
iex> n = 1
1
iex> map = %{n => :one}
%{1 => :one}
iex> map[n]
:one
iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three}
%{1 => :one, 2 => :two, 3 => :three}
The Map module provides a very similar API to the Keyword
module with convenience functions to manipulate maps:
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
Maps have the following syntax for updating a key’s value:
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{map | 2 => "two"}
%{2 => "two", :a => 1}
iex> %{map | :c => 3}
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
The syntax above requires the given key to exist. It cannot be used to add new keys. For example, using it with the :c
key failed because there is no :c
in the map.
When all the keys in a map are atoms, you can use the keyword syntax for convenience:
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
Another interesting property of maps is that they provide their own syntax for accessing atom keys:
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
Elixir developers typically prefer to use the map.field
syntax and pattern matching instead of the functions in the Map
module when working with maps because they lead to an assertive style of programming. This blog post by José Valim provides insight and examples on how you get more concise and faster software by writing assertive code in Elixir.
Nested data structures
Often we will have maps inside maps, or even keywords lists inside maps, and so forth. Elixir provides conveniences for manipulating nested data structures via the put_in/2
, update_in/2
and other macros giving the same conveniences you would find in imperative languages while keeping the immutable properties of the language.
Imagine you have the following structure:
iex> users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[
john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
We have a keyword list of users where each value is a map containing the name, age and a list of programming languages each user likes. If we wanted to access the age for john, we could write:
iex> users[:john].age
27
It happens we can also use this same syntax for updating the value:
iex> users = put_in users[:john].age, 31
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
The update_in/2
macro is similar but allows us to pass a function that controls how the value changes. For example, let’s remove “Clojure” from Mary’s list of languages:
iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}
]
There is more to learn about put_in/2
and update_in/2
, including the get_and_update_in/2
that allows us to extract a value and update the data structure at once. There are also put_in/3
, update_in/3
and get_and_update_in/3
which allow dynamic access into the data structure.
This concludes our introduction to associative data structures in Elixir. You will find out that, given keyword lists and maps, you will always have the right tool to tackle problems that require associative data structures in Elixir.
Ruby Equality tests
The Object class defines three equality-test methods: ==, eql?, and equal?. At the Object level, all equality-test methods do the same thing: they tell you whether two objects are exactly the same object. Here they are in action:
>> a = Object.new
=> #<Object:0x00000101258af8>
>> b = Object.new
=> #<Object:0x00000101251d70>
>> a == a
=> true
>> a == b
=> false
>> a != b
=> true
>> a.eql?(a)
=> true
>> a.eql?(b)
=> false
>> a.equal?(a)
=> true
>> a.equal?(b)
=> false
All three of the positive equality-test methods give the same results in these examples: when you test a against a, the result is true, and when you test a against b, the result is false. (The not-equal or negative equality test method != is the inverse of the == method; in fact, if you define ==, your objects will automatically have the != method.) We have plenty of ways to establish that a is a but not b.
But there isn’t much point in having three tests that do the same thing.
Further down the road, in classes other than Object, == and/or eql? are typically redefined to do meaningful work for different objects. For example, String redefines == and eql? to return whether the value of the strings being compared are identical. Two strings can have the same value but in fact be different objects. The equal? method retains its Object definition and checks if two strings are exactly the same object. Let’s look at string equality in action:
>> string1 = "text"
=> "text"
>> string2 = "text"
=> "text"
>> string1 == string2
=> true
>> string1.eql?(string2)
=> true
>> string1.equal?(string2)
=> false
As you can see, the strings are == and eql?, but not equal?. Ruby recommends against redefining equal? so that it can always be used to determine object identity.
Why do we have == and eql? if they’re synonymous at the Object level? Because it gives us more flexibility as we subclass Object. Because we don’t redefine equal?, we have the option to redefine either == or eql? and compare objects in different ways.
For example, in the Numeric class (a superclass of Integer and Float), == performs type conversion before making a comparison but eql? doesn’t:
>> 5 == 5.0
=> true
>> 5.eql? 5.0
=> false
Ruby gives you a suite of tools for object comparisons, and not always just comparison for equality.
Ruby remember a method name
Trying to remember a method name or just discover what you can call on an object? Ruby has you covered! Check out examples below
List all methods
User.methods
List all direct methods except Object class methods
User.methods - Object.methods
List all direct methods ( not comming from a parent class )
User.methods(false)
List all instance methods
User.instance_methods
List all direct instance methods
User.instance_methods(false)
List all methods and then grep on them
User.methods.grep(/find/)
=> [:find_for_database_authentication,
:find_for_authentication,
:find_first_by_auth_conditions,
:find_or_initialize_with_error_by,
:find_or_initialize_with_errors,
:_find_callbacks,
:_find_callbacks=,
:after_find,
:finder_needs_type_condition?,
:find_by!,
:cached_find_by_statement,
:find_by,
:initialize_find_by_cache,
:find,
:find_or_create_by,
:find_or_create_by!,
:find_or_initialize_by,
:create_or_find_by,
:create_or_find_by!,
:find_each,
:find_in_batches,
:find_by_sql]
Go Errors
In Go it’s idiomatic to communicate errors via an explicit, separate return value. This contrasts with the exceptions used in languages like Java and Ruby and the overloaded single result / error value sometimes used in C. Go’s approach makes it easy to see which functions return errors and to handle them using the same language constructs employed for any other, non-error tasks
By convention, errors are the last return value and have type error, a built-in interface.
errors.New constructs a basic error value with the given error message.
A nil value in the error position indicates that there was no error.
It’s possible to use custom types as errors by implementing the Error() method on them. Here’s a variant on the example above that uses a custom type to explicitly represent an argument error.
In this case we use &argError syntax to build a new struct, supplying values for the two fields arg and prob.
The two loops below test out each of our error-returning functions. Note that the use of an inline error check on the if line is a common idiom in Go code.
If you want to programmatically use the data in a custom error, you’ll need to get the error as an instance of the custom error type via type assertion.
package main
import (
"errors"
"fmt"
)
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 3, nil
}
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
$ go run errors.go
f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it
See this great post on the Go blog for more on error handling.
What is Proc in Ruby
A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Proc is an essential concept in Ruby and a core of its functional programming features.
square = Proc.new {|x| x**2 }
square.call(3) #=> 9
# shorthands:
square.(3) #=> 9
square[3] #=> 9
Proc objects are closures, meaning they remember and can use the entire context in which they were created.
def gen_times(factor)
Proc.new {|n| n*factor } # remembers the value of factor at the moment of creation
end
times3 = gen_times(3)
times5 = gen_times(5)
times3.call(12) #=> 36
times5.call(5) #=> 25
times3.call(times5.call(4)) #=> 60
Creation
There are several methods to create a Proc
- Use the Proc class constructor:
proc1 = Proc.new {|x| x**2 }
- Use the Kernel#proc method as a shorthand of ::new:
proc2 = proc {|x| x**2 }
- Receiving a block of code into proc argument (note the &):
def make_proc(&block)
block
end
proc3 = make_proc {|x| x**2 }
- Construct a proc with lambda semantics using the Kernel#lambda method (see below for explanations about lambdas):
lambda1 = lambda {|x| x**2 }
*Use the Lambda literal syntax (also constructs a proc with lambda semantics):
lambda2 = ->(x) { x**2 }
Lambda and non-lambda semantics
- Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:
- In lambdas, return means exit from this lambda;
*In regular procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method);
*In lambdas, arguments are treated in the same way as in methods: strict, with ArgumentError for mismatching argument number, and no additional argument processing;
*Regular procs accept arguments more generously: missing arguments are filled with nil, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments
p = proc {|x, y| "x=#{x}, y=#{y}" }
p.call(1, 2) #=> "x=1, y=2"
p.call([1, 2]) #=> "x=1, y=2", array deconstructed
p.call(1, 2, 8) #=> "x=1, y=2", extra argument discarded
p.call(1) #=> "x=1, y=", nil substituted instead of error
l = lambda {|x, y| "x=#{x}, y=#{y}" }
l.call(1, 2) #=> "x=1, y=2"
l.call([1, 2]) # ArgumentError: wrong number of arguments (given 1, expected 2)
l.call(1, 2, 8) # ArgumentError: wrong number of arguments (given 3, expected 2)
l.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
def test_return
-> { return 3 }.call # just returns from lambda into method body
proc { return 4 }.call # returns from method
return 5
end
test_return # => 4, return from proc
Lambdas are useful as self-sufficient functions, in particular useful as arguments to higher-order functions, behaving exactly like Ruby methods.
Procs are useful for implementing iterators:
def test
[[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
end
Inside map, the block of code is treated as a regular (non-lambda) proc, which means that the internal arrays will be deconstructed to pairs of arguments, and return will exit from the method test. That would not be possible with a stricter lambda.
You can tell a lambda from a regular proc by using the lambda? instance method.
Lambda semantics is typically preserved during the proc lifetime, including &-deconstruction to a block of code:
p = proc {|x, y| x }
l = lambda {|x, y| x }
[[1, 2], [3, 4]].map(&p) #=> [1, 2]
[[1, 2], [3, 4]].map(&l) # ArgumentError: wrong number of arguments (given 1, expected 2)
The only exception is dynamic method definition: even if defined by passing a non-lambda proc, methods still have normal semantics of argument checking.
class C
define_method(:e, &proc {})
end
C.new.e(1,2) #=> ArgumentError
C.new.method(:e).to_proc.lambda? #=> true
This exception ensures that methods never have unusual argument passing conventions, and makes it easy to have wrappers defining methods that behave as usual.
class C
def self.def2(name, &body)
define_method(name, &body)
end
def2(:f) {}
end
C.new.f(1,2) #=> ArgumentError
subscribe via RSS