elixir学习
文章目录
杂
使用 cmd 运行 elixir 语法:iex
在iex 中 使用 c “xxx.ex" 编译运行
编译运行:elixirc xxx.ex
mix new xxx 创建项目
iex -S mix 启动项目
使用 mix deps 查看依赖是否已添加
使用 mix deps.get 下载依赖
基础
原子:
原子类型是名字和代表的值相同的常量
to_charlist(atom): 将原子转换为charlist
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZAggZHx-1627371357025)(
)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-glT1EFrk-1627371357028)(
)]
to_string(atom): 将原子转换为字符串
字符串:
- <> 字符串拼接
- #{ var } : 字符串插值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uYwZb0q3-1627371357029)(
)]
iex(1)> string = <<104,101,108,108,111>>
"hello"
iex(2)> string
"hello"
iex(3)> string <> <<104>>
"helloh"
iex(6)> String.replace("Helloeee", "e", "a")
"Halloaaa"
列表:
列表是值的简单集合,可以包含不同的数据类型,而且也可能包含相同的值。
注:列表实现是链表,所以最好使用头插法添加元素
-
++:列表尾部拼接
-
– :列表减法:如果列表中有重复的值,对于右边列表中的出现的每一个值,只会从左边列表(被减列表)中移除一个与之相等的值,即第一个出现的值
-
| :添加元素是最好能用头插法
iex(17)> list = [1,2,3]
[1, 2, 3]
# 获取第一个元素
iex(18)> [head | list] = list
[1, 2, 3]
iex(19)> head
1
iex(20)> list
[2, 3]
iex(30)> list
[4, 2, 3]
iex(31)> [x,y,z | list] = list
[4, 2, 3]
iex(32)> z
3
iex(33)> list
[]
# 添加,删除
iex(21)> list ++ [4,5]
[2, 3, 4, 5]
iex(23)> list ++ 3
[2, 3 | 3]
iex(24)> list -- [2]
[3]
iex(35)> list = [3,2,1]
[3, 2, 1]
iex(36)> list -- [4]
[3, 2, 1]
# 判断是否在列表中
iex(25)> 3 in list
true
iex(26)> 2 in list
true
iex(27)> 1 in list
false
iex(28)> list = [4 | list]
[4, 2, 3]
- 列表打印
iex(1)> list = [1,2,3]
[1, 2, 3]
iex(2)> IO.puts list
:ok
iex(3)> IO.puts "#{inspect(list)}"
[1, 2, 3]
:ok
iex(4)>
元组:
元组和列表很像,但是元组在内存中是连续存放的。 这样的话,获取元组的长度很快,但是修改元组的操作很昂贵:新的元组必须重新在内存中拷贝一份。 定义元组要用花括号
iex(14)> {x,y} = {1,2}
{1, 2}
iex(15)> {z,t} = {1,2}
{1, 2}
iex(16)> {x,y} == {z,t}
true
注:一般使用元组进行成功匹配
iex(3)> {:ok,file} = File.open("demo_3.ex")
{:ok, #PID<0.116.0>}
关键字列表:
关键字列表本身是一个列表!只不过里面是k-v形式而已。
关键字列表里的内容是二元元组,并且二元组的第一个元素必须是原子!
iex(4)> list = [{:name, "Quin"},{:city, "Chendu"}]
[name: "Quin", city: "Chendu"]
iex(1)> aa = [name: "Quin", city: "Chendu"]
[name: "Quin", city: "Chendu"]
iex(2)> list = [{:name, "Quin"},{:city, "Chendu"}]
[name: "Quin", city: "Chendu"]
iex(3)> list == aa
true
iex(4)> list === aa
true
映射(散列表):
-
如果重复的键添加到映射中,后面的值会覆盖之前的值
-
存储键只有原子的映射,可以不用
=>
,直接使用关键字列表的语法 -
映射另一个有趣的特性是:它们提供了自己更新和获取原子键(key)的语法
iex(4)> m1 = %{"Quin" => "hello", :zzz => 123}
%{:zzz => 123, "Quin" => "hello"}
# 除原子以外,其他所有都只能 map[] 这种形式访问到值
iex(5)> m1["Quin"]
"hello"
iex(6)> m1.Quin
** (CompileError) iex:6: invalid alias: "m1.Quin". If you wanted to define an alias, an alias must expand to an atom at compile time but it did not, you may use Module.concat/2 to build it at runtime. If instead you wanted to invoke a function or access a field, wrap the function or field name in double quotes
# 原子可以使用 map. 进行访问
iex(6)> m1.zzz
123
iex(7)> m1[:zzz]
123
iex(8)> m1[zzz]
** (CompileError) iex:8: undefined function zzz/0
(stdlib 3.12) lists.erl:1354: :lists.mapfoldl/3
(stdlib 3.12) lists.erl:1355: :lists.mapfoldl/3
# 当全为原子的时候 可以简写
iex(12)> m2 = %{a: 1,b: 2,c: 3}
%{a: 1, b: 2, c: 3}
iex(13)> m2.a
1
# 更新散列表
iex(2)> list = [a: 1,b: 2]
iex(6)> map = Enum.into(list,%{c: 3})
%{a: 1, b: 2, c: 3}
iex(7)> map = %{map | c: 4 }
%{a: 1, b: 2, c: 4}
iex(8)> map
%{a: 1, b: 2, c: 4}
注意: 这种语法只在更新一个已经存在于映射的键才有效!如果键不存在,则会抛出 KeyError
错误。
iex(53)> func = fn a -> a + 3 end
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(54)> func.(1)
4
iex(55)> func_2 = &(&1 + &2 * &3 - &4)
#Function<5.126501267/4 in :erl_eval.expr/5>
iex(56)> func_2.(1,2,3,4)
3
Eenu 模块
- all?
使用 all?
以及大部分 Enum
函数的时候,我们要提供一个函数来作用到要操作的集合上。只有当函数在所有的元素上都返回 true
的时候,all?
才会返回 true
,否则结果就是 false
。
iex(1)> list = ["foo", "bar", "hello"]
["foo", "bar", "hello"]
iex(5)> Enum.all?(list, fn (s) -> String.length(s) > 1 end)
true
iex(2)> Enum.all?(list, &(String.length(&1) > 1))
true
iex(3)> func = &(String.length(&1) > 1)
#Function<7.126501267/1 in :erl_eval.expr/5>
iex(4)> Enum.all?(list, func)
true
- each
有时候需要遍历某个集合进行操作,但是不想产生新的值(不把函数的遍历调用结果返回),这种情况下,可以使用 each
iex(6)> Enum.each(list , &( IO.puts(&1) ))
foo
bar
hello
注意:each
函数会返回原子 :ok
- map
如果需要把执行结果做为一个新集合返回的话,可以使用map
函数
iex(7)> new_list = Enum.map(list , &( &1 <> " ,Quin" ))
["foo ,Quin", "bar ,Quin", "hello ,Quin"]
注:使用 Eenu 实现混合排序
- into
转换集合类型,也可以合并两个集合
@spec into(Enumerable.t(), Collectable.t(), (term() -> term())) :: Collectable.t()
iex(2)> list = [a: 1,b: 2]
[a: 1, b: 2]
iex(3)> Enum.into(list,%{})
%{a: 1, b: 2}
iex(4)> Enum.into(list,%{c: 3})
%{a: 1, b: 2, c: 3}
iex(5)> Enum.into(list,%{c: 3},fn {k, v} -> {k,v * 2} end)
%{a: 2, b: 4, c: 3}
- at
根据位置查询列表
@spec at(t(), index(), default()) :: element() | default()
iex(17)> list = [1,3,5,'a',:t]
[1, 3, 5, 'a', :t]
iex(18)> Enum.at list,3
'a'
- join
将列表转字符串
@spec join(t(), String.t()) :: String.t()
# 只能是列表
iex(13)> list
[1, 2, 3, 4]
iex(14)> Enum.join(list)
"1234"
iex(15)> Enum.join(list,",")
"1,2,3,4"
- 匿名函数使用
defmodule Test do
def format(x) do
x + 10
end
end
iex(3)> rows = [1,2,3,4,5]
[1, 2, 3, 4, 5]
iex(4)> Enum.map(rows,&Test.format/1)
[11, 12, 13, 14, 15]
iex(5)> Enum.map(rows,&Test.format(&1))
[11, 12, 13, 14, 15]
iex(6)> Enum.map(rows,&(Test.format(&1)))
[11, 12, 13, 14, 15]
iex(7)> Enum.map(rows,fn x -> Test.format(x) end)
[11, 12, 13, 14, 15]
模式匹配
在 Elixir 当中,用 =
来使得左右两边的值相等。如果匹配成功,即左右值相等后,返回这个等式的值。如果两边的值无法匹配,Elixir 则会抛出错误。
iex(30)> x = 1
1
iex(31)> 1 = x
1
iex(32)> 2 = x
** (MatchError) no match of right hand side value: 1
iex(32)> list = [1,2,3]
[1, 2, 3]
iex(33)> [x,y,z] = list
[1, 2, 3]
iex(34)> x
1
iex(35)> y
2
iex(40)> [1 | tail] = list
[1, 2, 3]
iex(41)> tail
[2, 3]
iex(42)>
- _ 通配符
iex(37)> [_,x,_] = [1,2,3]
[1, 2, 3]
iex(38)> x
2
- Pin 操作符:
使用 pin 操作符,我们就是用已经绑定的值去匹配,而不是重新绑定一个新值。
iex(42)> x = 1
1
iex(43)> {x,^x} = {2,1}
{2, 1}
iex(44)> x
2
iex(1)> greeting = "Hello"
"Hello"
iex(2)> greet = fn
...(2)> (^greeting, name) -> "Hi #{name}"
...(2)> (greeting, name) -> "#{greeting}, #{name}"
...(2)> end
#Function<13.126501267/2 in :erl_eval.expr/5>
iex(3)> greet.("hi","Quin")
"hi, Quin"
iex(5)> greeting = "hi"
"hi"
iex(6)> greet.("hi","Quin")
"hi, Quin"
iex(9)> greet.("Hello","Quin")
"Hi Quin"
iex(13)> greet.(greeting,"Quin")
"aaaa, Quin"
iex(14)> greet.("Hello","Quin")
"Hi Quin"
注:这里面^greeting 为 greeting 值
x = 3.14
IO.puts(x)
y = 10
x = y
IO.puts(x)
case y do
^x -> IO.puts("Not so tasty");x = 3.14;IO.puts(x)
x -> IO.puts("I bet #{x} is tasty")
end
iex(36)> c "demo_1.ex"
3.14
10
Not so tasty
3.14
[]
控制语句
Elixir 中唯一为假的值是 nil
和 布尔值 false
。其余都为真。
- if 和 unless
iex(15)> if :a do
...(15)> IO.puts("123")
...(15)> else
...(15)> IO.puts("456")
...(15)> end
123
:ok
iex(16)> unless is_integer("aaa") do
...(16)> "not an int"
...(16)> end
"not an int"
iex(17)> is_integer("aaa")
false
- case
case {:ok,"Hello Quin"} do
{:ok,res} -> IO.puts(res)
{:error} -> IO.puts("123456")
_ -> IO.puts("catch all")
end
iex(16)> c "demo_1.ex"
Hello Quin
[]
case {:ok} do
{:ok,res} -> IO.puts(res)
{:error} -> IO.puts("123456")
_ -> IO.puts("catch all")
end
iex(17)> c "demo_1.ex"
catch all
[]
case {"aaa"} do
{:ok,res} -> IO.puts(res)
{:error} -> IO.puts("123456")
{"aaa"} -> IO.puts("456789")
_ -> IO.puts("catch all")
end
iex(18)> c "demo_1.ex"
456789
[]
case {true} do
{:ok,res} -> IO.puts(res)
{:error} -> IO.puts("123456")
{"aaa"} -> IO.puts("456789")
{true} -> IO.puts("asd456789")
_ -> IO.puts("catch all")
end
iex(20)> c "demo_1.ex"
asd456789
[]
注:case 中的匹配参数必须相同,否则会匹配失败
- cond
当我们需要匹配条件而不是值的时候,可以使用 cond/1
,这和其他语言的 else if
或者 elsif
相似。
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
iex> cond do
...> 7 + 1 == 0 -> "Incorrect"
...> true -> "Catch all"
...> end
"Catch all"
函数
- 匿名函数
要定义匿名函数,我们需要 fn
和 end
关键字,在这两者之间,我们可以定义任意数量的参数和函数体,它们用 ->
分隔开。
iex(32)> sum = fn (a,b) -> a + b end
#Function<13.126501267/2 in :erl_eval.expr/5>
iex(33)> sum.(5,6)
11
iex(36)> sum = &(&1 + &2 + 1)
#Function<13.126501267/2 in :erl_eval.expr/5>
iex(37)> sum.(2,3)
6
- 命名函数
defmodule Zzz do
def func(n) do
"Hi," <> n
end
end
n = "Quin"
IO.puts(Zzz.func(n))
defmodule Length do
def of([]), do: 0
def of([_ | tail]), do: 1 + of(tail)
end
注:模块命必须为大写字母开头
- 函数与匹配模式
defmodule Greeter1 do
def hello(%{name: person_name}) do
IO.puts "Hello, " <> person_name
end
end
fred = %{ name: "Fred",age: "95",favorite_color: "Taupe" }
Greeter1.hello(fred)
iex(4)> c "demo_1.ex"
Hello, Fred
[Greeter1]
defmodule Greeter1 do
def hello(%{name: person_name}) do
IO.puts "Hello, " <> person_name
end
end
fred = %{ name1: "Fred",age: "95",favorite_color: "Taupe" }
Greeter1.hello(fred)
iex(5)> c "demo_1.ex"
warning: redefining module Greeter1 (current version defined in memory)
demo_1.ex:1
== Compilation error in file demo_1.ex ==
** (FunctionClauseError) no function clause matching in Greeter1.hello/1
The following arguments were given to Greeter1.hello/1:
# 1
%{age: "95", favorite_color: "Taupe", name1: "Fred"}
demo_1.ex:2: Greeter1.hello/1
(elixir 1.11.4) lib/kernel/parallel_compiler.ex:315: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
** (CompileError) compile error
(iex 1.11.4) lib/iex/helpers.ex:200: IEx.Helpers.c/2
注:这里的匹配必须是已有的 key 才行
- 私有函数
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello, "
end
iex> Greeter.hello("Sean")
"Hello, Sean"
- 哨兵子句
defmodule Greeter do
def hello(names) when is_list(names) do
names
|> Enum.join(", ")
|> hello
end
def hello(name) when is_binary(name) do
IO.puts(name)
phrase() <> name
end
defp phrase, do: "Hello, "
end
# IO.puts(Greeter.hello("123"))
IO.puts(Greeter.hello(["123","456","789"]))
- 默认参数
如果想给参数设置默认值,我们可以用 argument \\ value
语法。
defmodule Greeter do
def hello(name, language_code \\ "en") do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello("Sean", "en")
"Hello, Sean"
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.hello("Sean", "es")
"Hola, Sean"
defmodule Greeter do
def hello(names, language_code \\ "en")
def hello(names, language_code) when is_list(names) do
names
|> Enum.join(", ")
|> hello(language_code)
end
def hello(name, language_code) when is_binary(name) do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"
iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"
管道
Elixir 给我们提供了管道操作符来解决这个语法上的混乱。管道操作符 |>
获取一个表达式的结果,并把它往后传递。
other_function() |> new_function() |> baz() |> bar() |> foo()
管道获取左边的值,并把它传递给右边。
模块
- 模块属性
注:模块属性通常是常量不能改变
defmodule Example do
@greeting "Hello"
def greeting(name) do
~s(#{@greeting} #{name}.)
end
end
若要实现全局变量可用 GenServer 的状态实现
defmodule Registry do
use GenServer
# 外部API
def start_link() do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def lookup() do
GenServer.call(__MODULE__, :lookup)
end
@impl true
def init(:ok) do
count = %{lookup: 0}
{:ok, count}
end
@impl true
def handle_call(:lookup, _from, count) do
l = count.lookup + 1
count = %{count | lookup: l}
{:reply, l, count}
end
end
iex(2)> Registry.start_link
{:ok, #PID<0.112.0>}
iex(3)> Registry.lookup
1
iex(4)> Registry.lookup
2
组合
- alias
defmodule Sayings.Greetings do
def basic(name), do: "Hi, #{name}"
end
defmodule Example do
alias Sayings.Greetings
def greeting(name), do: Greetings.basic(name)
end
# Without alias
defmodule Example do
def greeting(name), do: Sayings.Greetings.basic(name)
end
# 有冲突时
defmodule Example do
alias Sayings.Greetings, as: Hi
def print_message(name), do: Hi.basic(name)
end
推导
# 关键字列表
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]
# 映射
iex> for {k, v} <- %{"a" => "A", "b" => "B"}, do: {k, v}
[{"a", "A"}, {"b", "B"}]
# 二进制
iex> for <<c <- "hello">>, do: <<c>>
["h", "e", "l", "l", "o"]
iex> for {:ok, val} <- [ok: "Hello", error: "Unknown", ok: "World"], do: val
["Hello", "World"]
- 过滤器
defmodule A do
def func do
import Integer
for x <- 1..100,is_even(x),rem(x,3) == 0,do: IO.puts(x)
end
end
A.func()
iex(1)> c "demo_1.ex"
6
12
18
24
...
90
96
[A]
创建项目
- 使用 mix new 创建项目
mix new example
- 交互 iex -S mix
这样 iex
启动的时候会把你的应用和依赖都加载到当前环境(这样你可以直接在 iex
中导入和运行编译好的代码)。
错误处理
- try/rescue/after
try do
raise "Oh no!"
rescue
e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
after
IO.puts("The end!")
end
iex(11)> c "demo_1.ex"
An error occurred: Oh no!
The end!
[]
并发
- 进程 spawn
@spec spawn(module(), atom(), list()) :: pid()
实现异步操作,返回 PID
defmodule Example do
def add(a, b) do
IO.puts(a + b)
end
end
spawn(Example, :add, [2, 3])
- 消息传递 send 和 receive
@spec send(dest :: Process.dest(), message) :: message when message: any()
defmodule Spawn do
def greet do
# receive 每次只能接受一条消息,若要处理多条数据需要递归操作
# receive 类似 case 语句
receive do
# receive 监听 与 send 一起使用
{sender, msg} ->
send(sender,{:ok,"hello1, #{msg}"})
send(sender,{:ok,"hello2, #{msg}"})
send(sender,{:ok,"hello3, #{msg}"})
# 客服端也只能每次接收一条消息
{:err} ->
IO.puts "err!"
end
end
end
iex(2)> pid = spawn(Spawn,:greet,[])
#PID<0.112.0>
iex(3)> send pid,{self,"Quin"}
{#PID<0.103.0>, "Quin"}
iex(4)> receive do
...(4)> {:ok,msg}->
...(4)> IO.puts msg
...(4)> end
hello1, Quin
:ok
iex(5)> send pid,{self,"Quin"}
{#PID<0.103.0>, "Quin"}
iex(6)> receive do
...(6)> {:ok,msg}->
...(6)> IO.puts msg
...(6)> end
hello2, Quin
:ok
iex(7)> receive do
...(7)> {:ok,msg}->
...(7)> IO.puts msg
...(7)> end
hello3, Quin
:ok
defmodule Spawn do
def greet do
# 循环接收多条消息
receive do
{:ok, msg} ->
IO.puts "get success! #{msg}"
{:err} ->
IO.puts "err!"
{sender,msg} ->
send sender,{:ok,"hello,#{msg}"}
_ ->
IO.puts "lalala~"
# 递归
end
greet()
end
end
iex(2)> pid = spawn(Spawn,:greet,[])
#PID<0.112.0>
iex(3)> send pid,{:ok,"aaa"}
get success! aaa
{:ok, "aaa"}
iex(4)> send pid,{:ok}
lalala~
{:ok}
iex(5)> send pid,{self,"Quin"}
{#PID<0.103.0>, "Quin"}
iex(6)> receive do
...(6)> {:ok,msg} ->
...(6)> IO.puts msg
...(6)> end
hello,Quin
:ok
iex(7)> receive do
...(7)> {:ok,msg} ->
...(7)> IO.puts msg
...(7)> after 500 ->
...(7)> IO.puts "the greeter has gone away"
...(7)> end
the greeter has gone away
:ok
注:若500ms 未接收到消息则退出接收状态,可使用 after
- elixir 递归:尾递归
defmodule A do
def func(n), do: _fact(n,1)
defp _fact(0,acc), do: acc
defp _fact(n,acc), do: _fact(n-1,acc*n)
end
defmodule B do
def func(0), do: 1
def func(n), do: n * func(n-1)
end
- 线程关联
用 spawn_link
把进程链接起来。两个链接起来的进程能收到相互的退出通知。
defmodule Example do
def explode, do: exit(:kaboom)
end
iex(3)> spawn Example,:explode,[]
#PID<0.141.0>
iex(4)> spawn_link Example,:explode,[]
** (EXIT from #PID<0.103.0>) shell process exited with reason: :kaboom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gj1MBp2l-1627371357031)(
)]
使用 spawn_link 时,当一个线程死亡时,另一个线程也会同时被杀死。
- 监控线程
使用 spawn_method 单向监控线程
节点
- 命名节点
- 查询节点名称
iex(2)> Node.self
:nonode@nohost
- 重命名节点名称
C:\xxx\Quin>iex --sname qiuran_node_one
Interactive Elixir (1.11.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(qiuran_node_one@Qiuran)1> Node.self
:qiuran_node_one@Qiuran
iex(qiuran_node_one@Qiuran)2>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4yzdQ2O-1627371357032)(
)]
- 节点添加
使用 Node.connect 连接节点
@spec connect(t()) :: boolean() | :ignored
# node_two
iex(node_two@Qiuran)1> Node.list
[]
iex(node_two@Qiuran)2> Node.connect :"node_one@Qiuran"
true
iex(node_two@Qiuran)3> Node.list
[:node_one@Qiuran]
# node_one
# 此时 node_one 也已经连接到 node_two 节点了
iex(node_one@Qiuran)1> Node.list
[:node_two@Qiuran]
- 使用 cookie 确保连接安全
iex --sname node_one -- cookie cookie-chip
iex --sname node_two -- cookie cookie-chip
# Node.get_cookie 查询 cookie 信息
iex(node_two@Qiuran)2> Node.get_cookie
:ERTBJEXYOWUNUKBQYGHC
iex(node_one@Qiuran)8> Node.get_cookie
:ERTBJEXYOWUNUKBQYGHC
# 若 cookie 信息不同,则无法连接成功
注:cookie 是明文传输的!
- Node.spawn 和 Noed.spawn_link
节点间的互相通信
Agent
iex(1)> {:ok, agent} = Agent.start_link fn -> [] end
{:ok, #PID<0.137.0>}
iex(2)> agent
#PID<0.137.0>
iex(3)> Agent.update(agent,fn list -> ["eggs" | list]end)
:ok
iex(4)> Agent.get(agent,fn list-> list end)
["eggs"]
iex(5)> Agent.update(agent,fn list -> ["aa" | list]end)
:ok
iex(6)> Agent.get(agent,fn list-> list end)
["aa", "eggs"]
iex(7)> Agent.stop(agent)
:ok
GenServer
defmodule Quin.Stack do
use GenServer
# 外部 API
# 连接
def start_link(:ok) do
GenServer.start_link(__MODULE__,:ok,name: __MODULE__)
end
def pop do
GenServer.call(__MODULE__,:pop)
end
def size do
GenServer.call(__MODULE__,:size)
end
def push(head) do
GenServer.call(__MODULE__,{:push,head})
end
def peek do
GenServer.call(__MODULE__,:peek)
end
# 初始化
@impl true
def init(:ok) do
{:ok, []}
end
# pop
# 获取当前栈第一个元素,并移除
@impl true
def handle_call(:pop, _from, state) do
# IO.puts
if is_list(state) and state === [] do
{:reply, {:ok,nil}, state}
else
[head | state] = state
{:reply, {:ok,head}, state}
end
end
# :size
# 获取当前栈大小
@impl true
def handle_call(:size, _from, state) do
{:reply, {:ok, length(state)}, state}
end
# :push
# 添加元素到栈中
@impl true
def handle_call({:push, head}, _from, state) do
state = [head | state]
{:reply, {:ok, true}, state}
end
# :peek
# 读取栈顶元素,但不移除
def handle_call(:peek, _from, state) do
head = hd(state)
{:reply, {:ok,head}, state}
end
end
mysql数据库 操作
添加 MyXQL 依赖
- 在 mix,exs 中添加 MyXQL 依赖
defp deps do
[
{:myxql, "~> 0.5.0"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
- 使用 mix deps 查看依赖是否已添加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bz0tEeeX-1627371357034)(
)]
- 使用 mix deps.get 下载依赖
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vakB6fr-1627371357035)(
)]
连接数据库
iex(2)> {:ok, pid} = MyXQL.start_link(hostname: "localhost",database: "test",username: "root",password: "root")
{:ok, #PID<0.214.0>}
注:myxql 中有很多参数需要是可进去查阅
数据库查询
- 查询
iex(3)> MyXQL.query(pid, "SELECT * FROM demo")
{:ok,
%MyXQL.Result{
columns: ["time", "content"],
connection_id: 4,
last_insert_id: nil,
num_rows: 3,
num_warnings: 0,
rows: [
[~N[2021-07-22 15:51:08],
"{ \"people\": [{ \"firstName\": \"Brett\", \"lastName\":\"McLaughlin\", \"email\": \"aaaa\" },{ \"firstName\": \"Jason\", \"lastName\":\"Hunter\", \"email\": \"bbbb\"},{ \"firstName\": \"Elliotte\", \"lastName\":\"Harold\", \"email\": \"cccc\" }]}"],
[~N[2021-07-09 15:52:11],
"{ \"people\": [{ \"firstName\": \"Brett\", \"lastName\":\"McLaughlin\", \"email\": \"aaaa\" },{ \"firstName\": \"Jason\", \"lastName\":\"Hunter\", \"email\": \"bbbb\"},{ \"firstName\": \"Elliotte\", \"lastName\":\"Harold\", \"email\": \"cccc\" }]}"],
[~N[2021-07-01 15:52:23],
"{ \"people\": [{ \"firstName\": \"Brett\", \"lastName\":\"McLaughlin\", \"email\": \"aaaa\" },{ \"firstName\": \"Jason\", \"lastName\":\"Hunter\", \"email\": \"bbbb\"},{ \"firstName\": \"Elliotte\", \"lastName\":\"Harold\", \"email\": \"cccc\" }]}"]
]
}}
- 插入
iex(10)> MyXQL.query(pid, "INSERT INTO demo (time,content) VALUES (?,?)",[~N[2021-07-22 15:51:08],"{ \"people\": [{ \"firstName\": \"Brett\", \"lastName\":\"McLaughlin\", \"email\": \"aaaa\" },{ \"firstName\": \"Jason\", \"lastName\":\"Hunter\", \"email\": \"bbbb\"},{ \"firstName\": \"Elliotte\", \"lastName\":\"Harold\", \"email\": \"cccc\" }]}"])
{:ok,
%MyXQL.Result{
columns: nil,
connection_id: 4,
last_insert_id: 0,
num_rows: 1,
num_warnings: 0,
rows: nil
}}
注:~N[2021-07-22 15:51:08]
~N:魔符 时间格式
综合项目:
本地 mysql 建一张表, 其中一个字段使用 datetime 类型其中一个字段使用 text 类型, 保存 json 格式的数据;
搭建一个 supervisor 项目, 利用这两个第三方库: myxql, jason 完成如下功能:
1.往数据库表中插入一行数据;
2.读取数据库数据并解析( json 字段) 返回.
-
创建 supervisor 项目 mix new --sup SqlDemo
-
配置 SqlDemo.Application
def start(_type, _args) do
children = [
%{
id: SqlDemo,
start: {SqlDemo, :start_link, [:ok]}
}
# Starts a worker by calling: SqlDemo.Worker.start_link(arg)
# {SqlDemo.Worker, arg}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: SqlDemo.Supervisor]
Supervisor.start_link(children, opts)
end
- 实现数据库操作
defmodule SqlDemo do
use GenServer
# 外部API
def start_link(:ok) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def select_json do
GenServer.call(__MODULE__, :select_json)
end
def select_all do
GenServer.call(__MODULE__, :select_all)
end
def insert(x) do
GenServer.call(__MODULE__, {:insert, x})
end
def count do
GenServer.call(__MODULE__, :count)
end
# 内部API
@impl true
def init(:ok) do
MyXQL.start_link(
hostname: "localhost",
database: "test",
username: "root",
password: "root",
name: :sql
)
count = %{all: 0, json: 0, insert: 0}
{:ok, count}
end
@doc """
获取所有访问次数
"""
@impl true
def handle_call(:count, _from, count) do
{:reply, count, count}
end
@doc """
查询所有
"""
@impl true
def handle_call(:select_all, _from, count) do
all_count = count.all + 1
count = %{count | all: all_count}
{:ok, res} = MyXQL.query(:sql, "SELECT * FROM demo")
{:ok, rows} = Map.fetch(res, :rows)
{:reply, rows, count}
end
@doc """
查询 json 格式数据
"""
@impl true
def handle_call(:select_json, _from, count) do
json_count = count.json + 1
count = %{count | json: json_count}
{:ok, res} = MyXQL.query(:sql, "SELECT * FROM demo")
{:ok, rows} = Map.fetch(res, :rows)
r = Enum.map(rows, &format/1)
{:reply, r, count}
end
@doc """
格式 json
"""
defp format(row) do
{:ok, res} = Jason.decode(tl(row))
res
end
@doc """
插入时间和json数据
"""
@impl true
def handle_call({:insert, x}, _from, count) do
insert_count = count.insert + 1
count = %{count | insert: insert_count}
str = x
{:ok, str} = Jason.encode(str)
day = DateTime.utc_now()
{:ok, res} = MyXQL.query(:sql, "INSERT INTO demo (time,content) VALUES (?,?)", [day, str])
{:reply, res, count}
end
end
Mix:命令
-
mix deps: 查看依赖
-
mix deps.get 下载依赖
-
mix hex.config mirror_url https://hexpm.upyun.com 更新镜像下载地址
-
mix deps.compile xxx 编译(xxx)依赖
-
mix deps.clean xxx 清空(xxx)依赖
l(:count, _from, count) do
{:reply, count, count}
end@doc “”"
查询所有
“”"
@impl true
def handle_call(:select_all, _from, count) do
all_count = count.all + 1
count = %{count | all: all_count}
{:ok, res} = MyXQL.query(:sql, “SELECT * FROM demo”)
{:ok, rows} = Map.fetch(res, :rows)
{:reply, rows, count}
end@doc “”"
查询 json 格式数据
“”"
@impl true
def handle_call(:select_json, _from, count) do
json_count = count.json + 1
count = %{count | json: json_count}
{:ok, res} = MyXQL.query(:sql, “SELECT * FROM demo”)
{:ok, rows} = Map.fetch(res, :rows)
r = Enum.map(rows, &format/1)
{:reply, r, count}
end@doc “”"
格式 json
“”"
defp format(row) do
{:ok, res} = Jason.decode(tl(row))
res
end@doc “”"
插入时间和json数据
“”"
@impl true
def handle_call({:insert, x}, _from, count) do
insert_count = count.insert + 1
count = %{count | insert: insert_count}
str = x
{:ok, str} = Jason.encode(str)
day = DateTime.utc_now()
{:ok, res} = MyXQL.query(:sql, “INSERT INTO demo (time,content) VALUES (?,?)”, [day, str])
{:reply, res, count}
end
end
## Mix:命令
- mix deps: 查看依赖
- mix deps.get 下载依赖
- mix hex.config mirror_url https://hexpm.upyun.com 更新镜像下载地址
- mix deps.compile xxx 编译(xxx)依赖
- mix deps.clean xxx 清空(xxx)依赖
- mix deps.update xxx 更新(xxx)依赖