Elixir,使用另一个模块的功能 [英] Elixir, using function from another module

查看:88
本文介绍了Elixir,使用另一个模块的功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对编程和灵丹妙药非常陌生。因此,我非常努力地学习。但是我遇到了麻烦。我在寻找如何在另一个模块中使用函数的方式。我正在构建将键值映射存储在内存中的Web服务器。为了使地图保持临时状态,Ive决定使用Agent。这是我的代码的一部分:

I am extremly new to the programming and to the elixir. So I am very exited to learn as much as i can. But ive got a trouble. I looking the way how to use my functions in another module. I am building the web-server wich stores the key-value maps in the memory. To keep the maps temporary Ive decided to use Agent. Here is the part of my code:

defmodule Storage do
  use Agent

  def start_link do
    Agent.start_link(fn -> %{} end, name: :tmp_storage)
  end

  def set(key, value) do
    Agent.update(:tmp_storage, fn map -> Map.put_new(map, key, value) end)
  end

  def get(key) do
    Agent.get(:tmp_storage, fn map -> Map.get(map, key) end)
  end
end

所以我试图将此功能放到Web服务器的路由上:

So I m trying to put this functions to the routes of the web server:

defmodule Storage_router do
  use Plug.Router
  use Plug.Debugger
  require Logger
  plug(Plug.Logger, log: :debug)
  plug(:match)
  plug(:dispatch)

  post "/storage/set" do
    with {:ok, _} <- Storage.set(key, value) do
      send_resp(conn, 200, "getting the value")
    else
      _ ->
        send_resp(conn, 404, "nothing")
    end
  end
end

我收到:

警告:变量 key不存在,并被扩展为 key( ),请使用括号消除歧义或更改变量名称
lib / storage_route.ex:12

警告:变量值不存在并且正在扩展为值(),请使用括号消除歧义或更改变量名称
lib / storage_route.ex:12

寻找任何建议\帮助

推荐答案


我是编程和灵丹妙药。

I am extremly new to the programming and to the elixir.

我认为这并不明智开始用长生不老药学习编程。我将从python或ruby开始,然后在一两年后再尝试长生不老药。

I do not think it is wise to begin learning programming with elixir. I would start with python or ruby, and then after a year or two then I would try elixir.

您需要学习的第一件事是如何发布代码。搜索google,了解如何在stackoverflow上发布代码。然后,您必须将所有缩进排成一行。您是否正在使用计算机编程文本编辑器?如果没有,那么你必须得到一个。有很多免费的。我使用vim,它像计算机一样安装在Unix上。您可以通过在终端窗口中键入 vimtutor 来学习如何使用vim。

The first thing you need to learn is how to post code. Search google for how to post code on stackoverflow. Then, you have to get your indenting all lined up. Are you using a computer programming text editor? If not, then you have to get one. There are many free ones. I use vim, which comes installed on Unix like computers. You can learn how to use vim by typing vimtutor in a terminal window.

接下来,您遇到语法错误在您的代码中:

Next, you have a syntax error in your code:

 Agent.start_link(fn -> %{} end, name: :tmp_storage
    end)  

应为:

 Agent.start_link(fn -> %{} end, name: :tmp_storage)

您得到的警告是因为您的代码尝试执行以下操作:

The warning you got is because your code tries to do the equivalent of:

def show do
   IO.puts x
end

Elixir和其他阅读该代码的人都会问: x到底是什么?变量x永远不会在任何地方分配值,因此变量x不存在,并且您不能输出不存在的内容。您在这里做同样的事情:

Elixir and anyone else reading that code would ask, "What the heck is x?" The variable x is never assigned a value anywhere, and therefore the variable x does not exist, and you cannot output something that is non-existent. You do the same thing here:

   with {:ok, _} <- Storage.set(key, value) do
     send_resp(conn, 200, "getting the value")
   else
     _->
      send_resp(conn, 404, "nothing")
   end

函数:

Storage.set(key, value)

,但从未分配变量 key value 值,以及长生不老药(以及其他阅读该代码的人)想知道,键和值到底是什么?

but the variables key and value were never assigned a value, and elixir (and anyone else reading that code) wonders, "What the heck are key and value?"

这是函数的工作方式:

b.ex:

defmodule MyFuncs do
  def show(x, y) do
    IO.puts x
    IO.puts y
  end
end

defmodule MyWeb do
  def go do
    height = 10
    width = 20

    MyFuncs.show(height, width)
  end
end

在iex中:

~/elixir_programs$ iex b.ex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> MyWeb.go
10
20
:ok
iex(2)> 

因此,在您的代码中,您需要编写如下内容:

So, in your code you need to write something like this:

post "/storage/set" do
  key = "hello"
  value = 10

  with {:ok, _} <- Storage.set(key, value) do
    send_resp(conn, 200, "Server saved the key and value.")
  else
    _->
      send_resp(conn, 404, "nothing")
  end
end

但是,它将为每个发布请求存储相同的键/值。大概您想将发送的内容存储在发布请求的正文中。您知道获取请求和发布请求之间的区别吗? get请求将数据附加到url的末尾,而post请求则将数据发送到请求的主体中,因此根据请求的类型,有不同的过程来提取数据。

However, that will store the same key/value for every post request. Presumably, you want to store whatever is sent in the body of the post request. Do you know the difference between a get request and a post request? A get request tacks data onto the end of the url, while a post request sends the data in the "body of the request", so there are different procedures for extracting the data depending on the type of the request.

您正在阅读什么教程?本教程: https: //www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/ ,向您展示如何从发布请求的正文中提取数据。发布请求正文中的数据只是一个字符串。如果字符串为JSON格式,则可以使用 Poison.decode!()将字符串转换为长生不老药图,这将使您轻松提取与之关联的值例如,

What tutorial are you reading? This tutorial: https://www.jungledisk.com/blog/2018/03/19/tutorial-a-simple-http-server-in-elixir/, shows you how to extract the data from the body of a post request. The data in the body of a post request is just a string. If the string is in JSON format, then you can convert the string into an elixir map using Poison.decode!(), which will allow you to easily extract the values associated with the keys that you are interested in. For example:

  post "/storage/set" do
    {:ok, body_string, conn} = read_body(conn)
    body_map = Poison.decode!(body_string)

    IO.inspect(body_map) #This outputs to terminal window where server is running 

    message = get_in(body_map, ["message"])    
    send_resp(
      conn, 
      201,
      "Server received: #{message}\n"
    )
  end

然后您可以在另一个命令中使用以下curl命令终端窗口向该路由发送发布请求:

Then you can use the following curl command in another terminal window to send a post request to that route:

$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'

-v = >详细输出, -H =>请求标头, -d =>数据)

(-v => verbose output, -H => request header, -d => data)

现在,基于我上面的代码所说的错误,您应该对此行感到疑惑:

Now, based on what I said was wrong with your code above, you should be wondering about this line:

{:ok, body_string, conn} = read_body(conn)

该行调用:

read_body(conn)

,但是变量 conn 并未在任何地方分配值。但是,Plug会无形地创建conn变量并为其分配一个值。

but the variable conn is not assigned a value anywhere. However, Plug invisibly creates the conn variable and assigns a value to it.

这里是使用Agent存储请求后数据的完整示例(按照我上面链接的教程) :

Here is a complete example using Agent to store post request data (following the tutorial I linked above):

simple_server
   config/
   lib/
       simple_server/
           application.ex
           router.ex
           storage.ex
   test/

一个长生不老药约定是在 lib / 目录中有一个与您的项目同名的目录,在本例中为simple_server,然后给您定义名称的模块反映目录结构。因此,在router.ex中,您将定义一个名为SimpleServer.Router的模块,在storage.ex中,您将定义一个名为SimpleServer.Storage的模块。但是,模块名称中的对Elixir而言并没有什么特别的,因此,如果您决定将模块命名为 FROGS ,则不会出错。 code>在文件 lib / rocks.ex 中-您的代码就可以正常工作。

An elixir convention is to have a directory in the lib/ directory with the same name as your project, in this case that would be simple_server, then you give the modules you define names that reflect the directory structure. So, in router.ex you would define a module named SimpleServer.Router and in storage.ex you would define a module named SimpleServer.Storage. However, the . in a module name means nothing special to elixir, so you will not get an error if you decide to name your module F.R.O.G.S in the file lib/rocks.ex--and your code will work just fine.

router.ex:

router.ex:

defmodule SimpleServer.Router do
  use Plug.Router
  use Plug.Debugger

  require Logger

  plug(Plug.Logger, log: :debug)
  plug(:match)
  plug(:dispatch)

  get "/storage/:key" do
    resp_msg = case SimpleServer.Storage.get(key) do
      nil -> "The key #{key} doesn't exist!\n"
      val -> "The key #{key} has value #{val}.\n"
    end

    send_resp(conn, 200, resp_msg)
  end

  post "/storage/set" do
    {:ok, body_string, conn} = read_body(conn)
    body_map = Poison.decode!(body_string)

    IO.inspect(body_map) #This outputs to terminal window where server is running 

    Enum.each(
      body_map, 
      fn {key, val} -> SimpleServer.Storage.set(key,val) end
    )

    send_resp(
      conn, 
      201,
      "Server stored all key-value pairs\n"
    )
  end

  match _ do
    send_resp(conn, 404, "not found")
  end


end

上面代码中首先要注意的是路线:

The first thing to note in the code above is the route:

get "/storage/:key" do

将匹配以下路径:

/storage/x 

,插件会创建一个变量能够命名的键,并为其分配值 x,如下所示:

and plug will create a variable named key and assign it the value "x", like this:

 key = "x"

另外,请注意,在调用函数时:

Also, note that when you call a function:

width = 10
height = 20
show(width, height)

elixir查看函数定义:

elixir looks at the function definition:

def show(x, y) do
  IO.puts x
  IO.puts y
end

并匹配函数调用像这样的定义:

and matches the function call to the def like this:

    show(width, height)
          |       |
          V       V
def show( x    ,  y) do
  ...
end

并执行任务:

 x = width
 y = height

然后,在函数内部可以使用x和y变量。在此行中:

Then, inside the function you can use the x and y variables. In this line:

    Enum.each(
      body_map, 

      #  | | | | |
      #  V V V V V

      fn {key, val} -> SimpleServer.Storage.set(key,val) end
    )

Elixir将调用匿名函数并传递 key 的值val ,像这样:

Elixir will call the anonymous function passing values for key and val, like this:

func("x", "10")

因此,在匿名函数的主体中,可以使用变量 key val

Therefore, in the body of the anonymous function you can use the variables key and val:

SimpleServer.Storage.set(key,val)

因为变量 key val 已经被分配了值。

because the variables key and val will already have been assigned values.

storage.ex:

storage.ex:

defmodule SimpleServer.Storage do
  use Agent

  def start_link(_args) do  #<*** Note the change here
    Agent.start_link(fn -> %{} end, name: :tmp_storage)
  end

  def set(key, value) do
    Agent.update(
      :tmp_storage, 
      fn(map) -> Map.put_new(map, key, value) end
    )
  end

  def get(key) do
    Agent.get(
      :tmp_storage, 
      fn(map) -> Map.get(map, key) end
    )
  end

end

application.ex:

application.ex:

defmodule SimpleServer.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do
    # List all child processes to be supervised
    children = [
      Plug.Adapters.Cowboy.child_spec(scheme: :http, plug: SimpleServer.Router, options: [port: 8085]),

      {SimpleServer.Storage, []}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: SimpleServer.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

mix.exs:

defmodule SimpleServer.MixProject do
  use Mix.Project

  def project do
    [
      app: :simple_server,
      version: "0.1.0",
      elixir: "~> 1.6",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {SimpleServer.Application, []}
    ]
  end


  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
        {:poison, "~> 4.0"},
        {:plug_cowboy, "~> 2.0"}

      # {:dep_from_hexpm, "~> 0.3.0"},
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
    ]
  end
end

请注意,如果您使用在教程中,您将收到一些警告,包括警告:

Note, if you use the dependencies and versions specified in the tutorial you will get some warnings, including the warning:

~/elixir_programs/simple_server$ iex -S mix
...
...

12:48:57.767 [warn]  Setting Ranch options together 
with socket options is deprecated. Please use the new
map syntax that allows specifying socket options 
separately from other options.

...这是一个问题插件,以下是我用来消除所有警告的依赖项和版本:

...which is an issue with Plug. Here are the dependencies and versions that I used to get rid of all the warnings:

   {:poison, "~> 4.0"},
   {:plug_cowboy, "~> 2.0"}

此外,当您将应用程序列为依赖项时,您不必再输入在:extra_applications 列表中,Elixir将在启动您的应用程序之前自动启动所有列为依赖项的应用程序。请参见:applications v。:extra_applications

Also, when you list an application as a dependency, you no longer have to enter it in the :extra_applications list. Elixir will automatically start all the applications listed as dependencies before starting your application. See :applications v. :extra_applications.

服务器启动后,您可以使用另一个终端窗口发送带有 curl 的帖子请求(或者您可以使用其他程序):

Once the server has started, you can use another terminal window to send a post request with curl (or you can use some other program):

~$  curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"x": "10", "y": "20" }

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> POST /storage/set HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 23
> 
* upload completely sent off: 23 out of 23 bytes
< HTTP/1.1 201 Created
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:23 GMT
< content-length: 34
< cache-control: max-age=0, private, must-revalidate
< 
Server stored all key-value pairs
* Connection #0 to host localhost left intact

> 行是请求,而< 行是响应。另外,在运行服务器的终端窗口中检查输出。

The > lines are the request, and the < lines are the response. Also, check the output in the terminal window where the server is running.

~$  curl -v http://localhost:8085/storage/z

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/z HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:30 GMT
< content-length: 25
< cache-control: max-age=0, private, must-revalidate
< 
The key z doesn't exist!
* Connection #0 to host localhost left intact

~$  curl -v http://localhost:8085/storage/x

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8085 (#0)
> GET /storage/x HTTP/1.1
> Host: localhost:8085
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< server: Cowboy
< date: Fri, 30 Nov 2018 19:22:37 GMT
< content-length: 24
< cache-control: max-age=0, private, must-revalidate
< 
The key x has value 10.
* Connection #0 to host localhost left intact

这篇关于Elixir,使用另一个模块的功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆