homedark

Elixir Tips and Tricks

Oct 27, 2017

1. Struct Update

The special syntax for updating a map, %{var | field: value} is particularly useful with structs.

With a plain map, the following gives a runtime erorr:

saiyan = %{name: "goku"}
saiyan = %{goku | pwer: 9001}

because %{var | ...} can only be used to update an existing field. However, with a struct, you'll get a compile-time error:

defmodule Saiyan do
  defstruct [:name, :power]
  ...
end

goku = %Saiyan{name: "goku"}
goku = %Saiyan{goku | pwer: 9001}

** (CompileError) code.ex:17: unknown key :pwer for struct Saiyan

2 alias, import and use

Starting off, it can be a overwhelming to realize that Elixir has at least three ways of interacting with other modules. While there's a bit of overlap, they mostly serve distinct purposes.

2.a alias

alias is the simplest to understand and the one you'll use the most. It lets you access a module via a shortened name rather than its full name:

MyApp.Service.Auth.user(email, password)

# vs
alias Myapp.Service.Auth
...
Auth.user(email, password)

2.b import

import takes this a step further: once imported, you can access functions directly as though they're defined in the current module:

import Myapp.Service.Auth
...
user(email, password)

Personally, I think you should avoid importing. It makes things obscure: where is user defined? Also, because you lose the context provided by the [hopefully] meaningful module name, for the sake of readability, you'll often have you re-add that context to the function name, such as auth_user.

Why bother? First, you have to import a module if you want to use its macros. As a beginner-safe-rule, you should only import when you need to use a module's macros. You can enforce this:

import MyApp.Service.Auth, only: :macros

You can also import only functions or specific functions or macros.

I confess that we do occasionally import functions. Most notably in our tests, where a number of helpers are repeatedly needed. For example, all of our integration tests have access to an imported truncate function which is used to wipe a database clean.

2.c use

use isn't like alias or import but it's so powerful that it can be, and often is, used to inject an alias and/or import. When you use a module, that module's __using__/1 macro is executed. That macro can do anything, which means the behavious of use changes from module to module.

defmodule MyApp.Controller.Base do
  # automatically called when you: use MyApp.Controller.Base
  defmacro __using__(_opts) do
    quote do
      # Gets injected into the calling module
      import MyApp.Controller.Base

      # ... probably does more things
    end
  end
end

Above is a somewhat common pattern where a use'd module imports itself. If you're just starting to learn elixir, use when a library / documentation tells you to, and take the opportunity to look at that module's __using__/1 macro to learn what it's doing.

3. alias __MODULE__, as...

You can minimize the presence of __MODULE__ by aliasing it:

defmodule DBZ.Saiyan do
  # Expands to alias DBZ.Saiyan, which means we can now use
  # Saiyan instead of __MODULE__
  alias __MODULE__

  def new(name) do
    %Saiyan{name: name}
  end
end

We often do this in our GenServers and give it the name of State (unless something more specific makes sense). This works well when combined with the special update syntax described in tip #1:

defmodule Dune.Harvesters do
  use GenServer
  alias __MODULE__, as: State

  defstruct [:lookup, :dispatched]

  # ...

  def handle_cast({:destroyed, id}, state) do
    state = %State{state | lookup: Map.delete(state.lookup, id)}
    {:noreply, state}
  end
end

4. With's default else behaviour

Elixir's with is useful for dealing with more complex flows, but did you know that you can omit the else clause? The default behaviour is to return whatever broke the flow (and would have triggered the else).

In other words, any time you write:

with {:ok, decoded} <- Poison.decode(data),
     {:ok, scores} <- extract_scores(decoded)
do
  # do something with scores
else
  err -> err
end

You can omit the else block:

with {:ok, decoded} <- Poison.decode(data),
     {:ok, scores} <- extract_scores(decoded)
do
  # do something with scores
end

5. Atom Memory

Atom's aren't garbage collected. You should be weary of using String.to_atom/1. Doing this on user input, for example, is a good way to run out of memory.

One option is to use String.to_existing_atom/1 which raises if the atom isn't already defined.

Another option is to leverage matching:

def planets("caladan"), do: :caladan
def planets("ix"), do: :ix
def planets("arrakis"), do: :arrakis
def planets(_), do: nil

Or less tedious and less readable:

for {value, input} <- [caladan: "caladan", ix: "ix", ...] do
  def planets(unquote(input)), do: unquote(value)
end
def planets(_), do: nil

6. IO.inspect Label

IO.inspect takes an optional label value which is prepends to the output:

IO.inspect("#{min} - #{max}", label: "debug")
> debug: "10 - 100"

7. IO.inspect return

Speaking of IO.inspect, it returns the parameter that you pass into it. This makes it injectable without having to change your code (say, by having to introduce a temporary variable or breaking a pipe chain):

case IO.inspect(parse_input(input)) do
  ...
end

# or
result = String.split(input, ",", parts: 3)
|> Enum.map(&String.to_integer/1)
|> IO.inspect()
|> ...

8. Running a Specific Test

Add the path to the mix test command to run that specific file and optionally include :LINE to run a specific test:

mix test test/users_test.exs
mix test test/users_test.exs:24

9. Dependencies

List outdated dependencies by running mix hex.outdated; clean unused dependencies with mix deps.clean --unlock --unused

10. Phoenix custom action parameters

If you find yourself using the same variable from conn.assigns, consider having it automatically injected into your actions:

# turn
def show(conn, params) do
  context = conn.assigns[:context]
  ...
end

# into
def show(conn, params, context) do
  ...
end

This can be achieved by overriding action/2 within your controller, (as described in the documentation):

def action(conn, _) do
  args = [conn, conn.params, conn.assigns[:context]]
  apply(__MODULE__, action_name(conn), args)
end

11. Default parameters and function heads

If you're new to elixir, you might see a function parameter which includes two backslashes: opts \\ []. This is how elixir defines a default value for a parameter.

Default values are related to another strange thing you might spot: functions with no bodies (called function heads). Consider the implementation of Enum.all/2:

def all?(enumerable, fun \\ fn(x) -> x end) # No body?!

def all?(enumerable, fun) when is_list(enumerable) do
  ...
end

def all?(enumerable, fun) do
  ...
end

That first line is required by the compiler whenever you use default values and have multiple versions of the function. It removes any ambiguity that might arise from having default values and multiple functions.

(Function heads are also useful in more advanced cases where documenting actual implementation(s) is messy or impractical, usually related to macros).

12. Pattern matching anonymous functions

Just like normal functions, anonymous functions can also do pattern matching:

def extract_errors(results) do
  Enum.reduce(results, [], fn
    :ok, errors -> errors  # don't do anything
    {:error, err}, errors -> [err | errors]
    other, errors ->  -> [other | errors]
  end)
end

13. Enum.reduce/3

Any function in the Enum module can be implemented using Enum.reduce/3. For example, Enum.map/2 is implemented as a reduce + reverse (since it preserves ordering).

You should always consider using the more readable versions, but if you're just getting started, it's a good mental exercise to consider how you'd implement each function using only reduce/3. Also, if performance matters and you have specific needs (like wanting to map but not caring about order), doing things in reduce/3 might be faster.

14. Don't overlook Enum.reduce_while/3

Enum.reduce_while/3 is a powered-up version of reduce/3. It behaves almost the same, including taking the same parameters, but you control when it should stop enumerating the input. This makes it a more efficient solution for implementing Enum.find/2, Enum.take_while/2 and any custom partial enumeration behaviour you need.

In the normal reduce/3 the supplied function returns the accumulator, which is passed to the next iteration. With reduce_while/3 the function returns a tuple, either: {:cont, acc} or {:halt, acc}. The values :cont and :halt control whether iteration should continue or halt. These values are stripped from the final return.

For example, say we're dealing with user input. We're getting an array of strings. We want to convert them into integers and limit the array to 10 items:

{_count, ids} = Enum.reduce_while(param["ids"], {0, []}, fn
  id, {11, ids} -> {:halt, {10, ids}} # or counter is > 10, stop processing
  id, {count, ids} ->
    case Integer.parse(id) do
      {n, ""} -> {:cont, {count + 1, [n | ids]}}
      _ -> {:cont, {count, ids}}
    end
end)

15. Process Mailboxes

The most important thing to understand about processes is how they interact with their mailbox. Every process has a queue of pending messages called a mailbox. When you send a message to a process (say via send/2, GenServer.call/2 or GenServer.cast/2), that message is added at the back of the target process' queue.

When you receive you dequeue the oldest message from the mailbox. For a GenServer receiving happens automatically; but the important point is that one message is processed at a time. It's not until you return from your handle_call/3, handle_cast/2 or handle_info/2 function that the next pending message will be processed. This is why your processes state is always consistent, it's impossible to have two concurrent threads of execution within the same process overwriting each other's changes to your state.

Whether you're using GenServer.cast/2 or call/2 doesn't change anything from the target process' point of view. The difference between the two only impacts the callers behaviour. If you cast and then call, you're guaranteed that the handle_cast will fully execute before the handle_call and thus the handle_call will see any state changes made by the handle_cast

16. GenServer's init

A failure in your GenServre's init/1 will take down your entire app. The behaviour might surprise you at first, but it isn't without benefits: it provides a strong guarantee about the state of your app. This is particularly true given that supervisors and their workers are started synchronously. In other words, if you have a supervisor tree that places WorkerB after WorkerA, then WorkerB can be sure that WorkerA has been fully initialized (and thus can call it).

If you absolutely need a database connection for your app to work, establish it in init. However, if you're able to gracefully handle an unavailble database, you should establish it outside of your init function. A common pattern is to send yourself a message:

def init(_) do
  send(self(), :connect)
  {:ok, nil}
end

def handle_info(:connect, state) do
  ...
  {:noreply, new_state}
end

Because processes only handle one message at a time, you could simply block in the above handle_info/2 until a connection can be established. Any call/2 or cast/2 into this process will only be processed once the above function returns. Of course, by default, call/2 will timeout after 5 seconds (which is probably what you want: the app will startup, but trying to use the features that rely on this process will error).

17. nil

There are a couple properties of nil that you should be aware or. First, because nil, like true and false, is actually an atom, it participates in term ordering rules:

nil > 1
> true

nil > "1"
> false

nil > :dune
> true

nil > :spice
> false

Secondly the Access behaviour (var[:key]) ignores nils. So while many languages would throw an exception on the following code, Elixir doesn't:

user = nil
user[:power]
> nil

user[:name][:last]
> nil

18. Sigils

Strings that embed quotes can be messy to write and hard to read. Sigils are special functions that begin with ~ aimed at helping developers deal with special text.

The most common is the ~s sigil which doesn't require quotes to be escaped:

"he said \"it's over 9000!!\""

# vs

~s(he said "it's over 9000")

The difference between the lowercase s and uppercase S sigils is that the lowercase one allows escape characters and interpolation:

~s(he said:\n\t"it's over #{power}")
> he said:
  "it's over 9000"

# vs

~S(he said:\n\t"it's over #{power}")
> he said:\n\t\"it's over \#{power}\"

The other popular sigil is ~r to create a regular expression:

Regex.scan(~r/over\s(\d+)/, "over 9000")
> [["over 9000", "9000"]]

Finally, you can always create your own sigils.

19. [Linked] Lists

Lists in Elixir are implemented as linked lists. In the name of performance, this is something you should always be mindful of. Many operations that are O(1) in other languages, are O(N) in Elixir. The three that you'll need to be most vigilant about are: getting the count/length, getting a value at a specific index and appending a value at the end of the list. For example, if you want to know if a list is empty, use Enum.empty?/1 rather than Enum.count/1 == 0 (or match against []code>).

While appending a value is O(N), prepending is O(1) - the opposite of what you'd see in languages with dynamic arrays. For this reason, when dealing with multiple values, you should favour prepending (+ reversing if order matters).

There are also cases where Erlang's double-ended queue might prove useful. It has O(1) operation to get and add values at both ends of the queue. Sadly, it still doesn't have an O(1) length operation, but you could easily create your own wrapper.

20. iolists

Many language us a buffered string when dealing building a string dynamically. While that isn't an option given immutable data structure, we do have a suitable alternative: iolists. An iolist is a list made of binaries (strings) or nested iolists. Here's a simple example, but keep in mind that iolists are often deeply nested:

sql = ["select ", ["id", " ,name", " ,power"], " from saiyans"]

Much of the standard library and many third party libraries can work with iolists directly:

IO.puts(["it's", " ", ["over ", ["9000", "!!!"]]])
> it's over 9000!!!

File.write!("spice", ["the", [" ", "spice", [" must", [" flow"]]]])
IO.inspect(File.read!("spice"))
> "the spice must flow"\

In some cases functions that receive an iolist will first convert it to a normal binary then process it (which is what Poison.decode/1 does if you pass it an iolist). However, in other cases, processing happens on the iolist directly.

Taking a step back, let's say we have a list of words:

colors = ["red", "blue", "green", "orange", "yellow"]

To append "grey" via the ++ operator, we need to create a new list and copy all the values into the new list. That's why we say it's O(N). Now consider what needs to happen if we do this as an iolist. The result would look like:

colors = [["red", "blue", "green", "orange", "yellow"], "grey"]

The original list doesn't change and so its values aren't copied. We do create a new list (the outer brackets). That new list references the original list at its head, and the new value at its tail. The cost of an iolist is no longer tied to the number of elements in the lists.

21. Piping

Don't do single statement piping. It's the bad sort of clever. This:

params = params |> Map.delete(:id)

Is less readable than this:

params = Map.delete(params, :id)

It's never called for.

"But the grooms, or 'bettos,' as the Japanese call them, are not the only ones who indulge in tattooing. You will see many of the 'sendos,' or boat-coolies, thus marked, but in a less degree than the bettos. Perhaps it is because the grooms are obliged to run so much, and consequently wish to lay aside all garments. As they must wear something, they have their skins decorated in this way, and thus have a suit of clothing always about them. Joe Johnston's shattered army was at Jackson, about forty-five miles to northward; beleaguered Vicksburg was in the Northwest, a trifle farther away; Natchez lay southwest, still more distant; and nearly twice as far in the south was our heartbroken New Orleans. We had paused to recuperate our animals, and there was a rumor that we were to get new clothing. Anyhow we had rags with honor, and a right to make as much noise as we chose. "Nonsense, my dear fellow. Now let me open your eyes. Behold the great force of a man who is gifted with second sight. Where did you get those notes? Was it not on the same evening as the murder?" "Oh, nothing," came the reply. "Only I was so silly as to place the wrong end of my cigarette in my mouth and burnt my lips. What's tuberose?" Reviewing these mechanical conditions, we may at once see sufficient reasons for the platen movement of planing machines; and that it would be objectionable, if not impossible, to add a traversing or cutting action to tools already supported through the medium of eight joints. To traverse for cutting would require a moving gib joint in place of the bolted one, between the standards and main frame, leading to a complication of joints and movements quite impracticable. And disadvantaged aye begins the strife. We have already seen how this fundamental division is applied to the universe as a whole. But our philosopher is not content with classifying the phenomena as he finds360 them; he attempts to demonstrate the necessity of their dual existence; and in so doing is guilty of something very like a vicious circle. For, after proving from the terrestrial movements that there must be an eternal movement to keep them going, he now assumes the revolving aether, and argues that there must be a motionless solid centre for it to revolve round, although a geometrical axis would have served the purpose equally well. By a still more palpable fallacy, he proceeds to show that a body whose tendency is towards the centre, must, in the nature of things, be opposed by another body whose tendency is towards the circumference. In order to fill up the interval created by this opposition, two intermediate bodies are required, and thus we get the four elementsearth, water, air, and fire. These, again, are resolved into the antithetical couples, dry and wet, hot and cold, the possible combinations of which, by twos, give us the four elements once more. Earth is dry and cold, water cold and wet, air wet and hot, fire hot and dry; each adjacent pair having a quality in common, and each element being characterized by the excess of a particular quality; earth is especially dry, water cold, air wet, and fire hot. The common centre of each antithesis is what Aristotle calls the First Matter, the mere abstract unformed possibility of existence. This matter always combines two qualities, and has the power of oscillating from one quality to another, but it cannot, as a rule, simultaneously exchange both for their opposites. Earth may pass into water, exchanging dry for wet, but not so readily into air, which would necessitate a double exchange at the same moment. 192 He helped her out. "I have drifted in a way," he went on to explain. "I left home when I was a mere boy, and the spirit of savagery and unrest laid hold of me. I can't break away. And I'm not even sure that I want to. You, I dare say, can't understand." Yet he felt so sure, for some reason, that she could that he[Pg 71] merely nodded his head when she said briefly, "I can." "Then, too," he went on, "there is something in the Indian character that strikes a responsive chord in me. I come of lawless stock myself. I was born in Sidney." Then he stopped short. What business was it of hers where he had been born? He had never seen fit to speak of it before. Nevertheless he intended that she should understand now. So he made it quite plain. "Sidney was a convict settlement, you know," he said deliberately, "and marriages were promiscuous. My grandfather was an officer who was best away from England. My grandmother poisoned her first husband. That is on my mother's side. On my father's side it was about as mixed." He leaned back, crossing his booted legs and running his fingers into his cartridge belt. His manner asked with a certain defiance, what she was going to do about it, or to think. "Naw; git out. Don't bother me with no questions, I tell you," impatiently said a man in citizen's clothes, who with arms outspread was signalling the switching engines. "'Tain't my business to give information to people. Got all I kin do to furnish brains for them bull-headed engineers. Go to that Quartermaster you see over there in uniform. The Government pays him for knowin' things. It don't me." As little Pete dropped to the ground, his nervous finger touched the trigger and his gun went off up in the air. The others took this as a cue, and banged away as rapidly as they could get their muskets off. "I am no master," Cadnan said wearily. "I am a slave." "The people know," Dodd said. "It's out. It's all out. About the slavery. Is that what you mean?" Cadnan peered at him, half-fearfully. "You are a master." One did not give orders to masters, or argue with them. "Why not? There's naun shameful in it. Munds's brother did it for twenty years. And think of the difference it'll m?ake to usthirty pound or so a year, instead of the dead loss of Harry's keep and the wages of an extra man beside. I tell you, mother, I wur fair sick about the farm till I thought of this." "Ben, I swear I'm your true wife." Reuben started, and Pete awoke noisily. Harry was frightened and dropped his string, crying because he could not find it. The knock came again, and this time Pete crossed the room yawning, and opened the door. www.715524.com.cn
www.gearmachine.com.cn
zjwfa.com.cn
jkkv.com.cn
www.kabil.com.cn
qttx.net.cn
xkgpss.com.cn
www.bwkp029.com.cn
yvhd.com.cn
www.xinhw.org.cn
中韩欧美一级一中文字暮 _黄片BB亚洲AV无码天堂www亚洲国产韩国欧美在线不卡一级 _毛片机地男男性行为免费视频播放九九欧美一级毛欧美片 _啪拍看片久色综合免费福利视频玖玖60岁欧美老妇一级毛 中韩欧美一级一中文字暮 中文字幕第十九页 中央游月中文字幕 中文字幕 第9页 中文字幕午夜福利 黄片BB亚洲AV无码天堂www亚洲国产韩国欧美在线不卡一级 中文字幕欧美日韩 中村知惠中文字幕 啪啪啪在线视频 中文字幕亚洲综合 中文字幕先锋资源 中文字幕 青青草 中文字幕资源网站 中韩欧美一级一中文字暮 中文字幕卡通动漫 中文字幕理论电影 免费视频播放 中文字幕资源在线 毛片免费观看 黄色一级电影片 中国美女一级看片 中文字幕先锋影音 黄片BB亚洲AV无码天堂www亚洲国产韩国欧美在线不卡一级 欧美在线不卡 中文字幕巨乳有码 啪拍看片久色综合免费福利视频玖玖60岁欧美老妇一级毛 中文字幕第86页 中文字幕91在线 黄色一级录像片 中文字幕久荜在线 黄色一级欧美片 毛片视频在线 中文字幕 第7页 中文字幕每日更新 毛片免费试看 中文字幕巨乱亚洲 中韩欧美一级一中文字暮 啪拍看片久色综合免费福利视频玖玖60岁欧美老妇一级毛 中韩欧美一级一中文字暮 毛片机地男男性行为免费视频播放九九欧美一级毛欧美片 黄色一级录像带 中文在线视频观看 中文字幕Av电影 啪啪啪男女视频 中文字幕 新妈妈 中国一级特黄大片 中文字幕久久视频 中文字幕黄色视频 中国黄色一级大片 中文字幕手机看片 中文字幕伦理在线 欧美一级毛 中韩欧美一级一中文字暮 免费视频播 啪啪免费视频网站 中文字幕男人天堂 黄色一级伦理片 中文字幕亚洲在线 中文字幕视频不卡 毛片免费网址 中国一级黄色大片 中文字幕亚洲情字 啪啪视频在线播放 啪拍看片久色综合免费福利视频玖玖60岁欧美老妇一级毛 中韩欧美一级一中文字暮 啪啪免费在线视频 亚洲AV无码天堂 中文字幕丝袜美腿 毛片免费视频 毛片在线电影 毛片在线不卡 啪啪啪在线播放 中文字幕伦理电影 中文字幕中文字幕 中文字幕一级在线 毛片免费基地 中文字幕综合影院 中文字幕手机在线 中文字幕视频在线 毛片机地男男性行为免费视频播放九九欧美一级毛欧美片 中国一级特大黄片 中文字幕乱码视频 中国成人在线视频 啪啪啪视频欧美 中国一级黄色电影 啪啪啪性爱动态图 啪啪啪欧美视频 黄色一级片播放 中文字幕乱码免费 黄片BB亚洲AV无码天堂www亚洲国产韩国欧美在线不卡一级 黄色一级毛毛片 中文娱乐在线视频 黄色一级黄色片 中文字幕乱伦电影 黄色一级片aa 中文字幕第十七页 男男性行为免费视频播放 黄色一级片观看 毛片免费电影
кŷһһĺ _ƬBBAVwww޹ŷ߲һ _ëƬΪƵžžŷһëŷƬ _žĿƬɫۺѸƵ60ŷϸһë кŷһһĺ Ļʮҳ Ļ Ļ 9ҳ Ļҹ ƬBBAVwww޹ŷ߲һ Ļŷպ д֪Ļ žžžƵ Ļۺ ĻȷԴ Ļ ĻԴվ кŷһһĺ Ļͨ Ļ۵Ӱ Ƶ ĻԴ ëƬѹۿ ɫһӰƬ йŮһƬ ĻȷӰ ƬBBAVwww޹ŷ߲һ ŷ߲ Ļ žĿƬɫۺѸƵ60ŷϸһë Ļ86ҳ Ļ91 ɫһ¼Ƭ Ļ ɫһŷƬ ëƬƵ Ļ 7ҳ Ļÿո ëƬԿ Ļ кŷһһĺ žĿƬɫۺѸƵ60ŷϸһë кŷһһĺ ëƬΪƵžžŷһëŷƬ ɫһ¼ Ƶۿ ĻAvӰ žžžŮƵ Ļ йһػƴƬ ĻþƵ ĻɫƵ йɫһƬ ĻֻƬ Ļ ŷһë кŷһһĺ Ƶ žžƵվ Ļ ɫһƬ Ļ ĻƵ ëƬַ йһɫƬ Ļ žžƵ߲ žĿƬɫۺѸƵ60ŷϸһë кŷһһĺ žžƵ AV Ļ˿ ëƬƵ ëƬߵӰ ëƬ߲ žžž߲ ĻӰ ĻĻ Ļһ ëƬѻ ĻۺӰԺ Ļֻ ĻƵ ëƬΪƵžžŷһëŷƬ йһشƬ ĻƵ йƵ žžžƵŷ йһɫӰ žžž԰̬ͼ žžžŷƵ ɫһƬ Ļ ƬBBAVwww޹ŷ߲һ ɫһëëƬ Ƶ ɫһɫƬ Ļ׵Ӱ ɫһƬaa Ļʮҳ ΪƵ ɫһƬۿ ëƬѵӰ
ENTER NUMBET 0018