首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用来自另一个模块的函数

使用来自另一个模块的函数
EN

Stack Overflow用户
提问于 2018-11-30 14:00:03
回答 3查看 1.9K关注 0票数 3

我对编程和灵丹妙药非常陌生。因此,我非常兴奋地尽可能多地学习。但我有麻烦了。我想知道如何在另一个模块中使用我的函数。我正在构建一个web服务器,它将键值映射存储在内存中。为了保持地图的临时性,我决定使用特工。下面是我代码的一部分:

代码语言:javascript
复制
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服务器的路由上:

代码语言:javascript
复制
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/存储路由。12:12 警告:变量"value“不存在,并且正在展开为"value()",请使用括号消除歧义或更改变量名。 lib/存储路由。12:12

寻求任何建议\帮助

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-11-30 15:37:25

我对编程和灵丹妙药非常陌生。

我认为开始使用长生不老药学习编程是不明智的。我从蟒蛇或红宝石开始,过了一两年,我就试一试长生不老药。

您需要了解的第一件事是如何发布代码。搜索谷歌如何在堆栈溢出上张贴代码。然后,你必须把你的缩进全部排好。您正在使用计算机编程文本编辑器吗?如果没有,那你就得买一个。有很多免费的。我使用vim,它像计算机一样安装在Unix上。您可以通过在终端窗口中键入vimtutor来学习如何使用vim。

接下来,代码中有一个语法错误:

代码语言:javascript
复制
 Agent.start_link(fn -> %{} end, name: :tmp_storage
    end)  

这应该是:

代码语言:javascript
复制
 Agent.start_link(fn -> %{} end, name: :tmp_storage)

您收到的警告是因为您的代码试图执行相当于:

代码语言:javascript
复制
def show do
   IO.puts x
end

灵丹妙药和其他阅读这段代码的人都会问:“x是什么?”变量x从未在任何地方分配值,因此变量x不存在,并且不能输出不存在的值。你在这里做同样的事情:

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

你可以调用这个函数:

代码语言:javascript
复制
Storage.set(key, value)

但是变量keyvalue从未被分配过一个值,而灵丹妙药(以及其他阅读该代码的人)会想,“关键和值到底是什么?”

这就是函数的工作方式:

b.ex:

代码语言:javascript
复制
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:

代码语言:javascript
复制
~/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)> 

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

代码语言:javascript
复制
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

但是,这将为每个post请求存储相同的键/值。想必,您希望将发送的任何内容存储在post请求的正文中。你知道get请求和post请求之间的区别吗?get请求将数据插入url的末尾,而post请求则将数据发送到“请求体”中,因此根据请求的类型,有不同的提取数据的过程。

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

代码语言:javascript
复制
  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命令向该路由发送post请求:

代码语言:javascript
复制
$ curl -v -H 'Content-Type: application/json' "http://localhost:8085/storage/set" -d '{"message": "hello world" }'

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

现在,根据我在上面的代码中所说的错误,您应该对这一行感到好奇:

代码语言:javascript
复制
{:ok, body_string, conn} = read_body(conn)

这句话叫:

代码语言:javascript
复制
read_body(conn)

但是变量conn在任何地方都没有被分配值。但是,插件无形中创建了conn变量,并为其赋值。

下面是一个使用代理存储post请求数据的完整示例(按照我在上面链接的教程):

代码语言:javascript
复制
simple_server
   config/
   lib/
       simple_server/
           application.ex
           router.ex
           storage.ex
   test/

灵丹妙药约定是在lib/目录中有一个与您的项目同名的目录,在本例中是simple_server,然后给出您定义的反映目录结构的模块名称。因此,在router.ex中,您将定义一个名为SimpleServer.Router的模块,而在storage.ex中,您将定义一个名为SimpleServer.Storage的模块。但是,模块名称中的.对灵丹妙药没有什么特殊意义,所以如果您决定在文件lib/rocks.ex中命名模块F.R.O.G.S,那么您的代码就不会出错--而且您的代码也会工作得很好。

router.ex:

代码语言:javascript
复制
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

以上代码中要注意的第一件事是路由:

代码语言:javascript
复制
get "/storage/:key" do

将匹配如下的路径:

代码语言:javascript
复制
/storage/x 

插件将创建一个名为key的变量,并将其赋值为"x",如下所示:

代码语言:javascript
复制
 key = "x"

另外,请注意,当调用一个函数时:

代码语言:javascript
复制
width = 10
height = 20
show(width, height)

“灵丹妙药”查看了函数定义:

代码语言:javascript
复制
def show(x, y) do
  IO.puts x
  IO.puts y
end

并将函数调用匹配到def,如下所示:

代码语言:javascript
复制
    show(width, height)
          |       |
          V       V
def show( x    ,  y) do
  ...
end

并执行以下任务:

代码语言:javascript
复制
 x = width
 y = height

然后,在函数中可以使用x和y变量。在这一行:

代码语言:javascript
复制
    Enum.each(
      body_map, 

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

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

灵丹妙药将调用传递keyval值的匿名函数,如下所示:

代码语言:javascript
复制
func("x", "10")

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

代码语言:javascript
复制
SimpleServer.Storage.set(key,val)

因为变量keyval已经被赋值了。

storage.ex:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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

注意,如果您使用本教程中指定的依赖项和版本,您将得到一些警告,包括警告:

代码语言:javascript
复制
~/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是插头的一个问题。下面是我用来消除所有警告的依赖项和版本:

代码语言:javascript
复制
   {:poison, "~> 4.0"},
   {:plug_cowboy, "~> 2.0"}

此外,当您将应用程序作为依赖项列出时,您不再需要在:extra_applications列表中输入它。在启动应用程序之前,灵丹妙药将自动启动所有作为依赖项列出的应用程序。见应用程序

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

代码语言:javascript
复制
~$  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

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

代码语言:javascript
复制
~$  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

代码语言:javascript
复制
~$  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
票数 6
EN

Stack Overflow用户

发布于 2018-11-30 14:11:46

我不完全确定您想要完成什么,但是错误告诉您,传递给路由器key语句的valuewith语句没有定义。“灵丹妙药”认为您试图用这些参数调用函数,因为它们没有“绑定”到某个值。这就是你看到warning: variable "value" does not exist and is being expanded to "value()"的原因

我想这并不是一个真正的答案,但也许更多的是对你所看到的错误的解释。

票数 4
EN

Stack Overflow用户

发布于 2018-11-30 17:51:04

您需要从%Plug.Conn{}对象(康涅狄格)中提取键/值参数。密钥/值变量尚未在路由范围内定义。conn对象之所以可用,是因为它是由插件提供的post宏注入的。

我不太清楚您要向路由器提交哪种类型的请求,但我假设它是JSON作为示例。您可以通过执行以下操作来手动解析连接中的主体:

代码语言:javascript
复制
with {:ok, raw_body} <- Plug.Conn.read_body(conn),
     {:ok, body} <- Poison.decode(raw_body) do
  key = Map.get(body, "key")
  value = map.get(body, "value")
  # ... other logic
end

但是,插件项目为您提供了一个很好的方便插件,可以以通用的方式解析请求体:Plug.Parsers

要在路由器中实现这一点,只需将插件添加到路由器的顶部(在Plug.Logger下面,我认为):

代码语言:javascript
复制
plug Plug.Parsers, 
  parsers: [:urlencoded, :json]
  json_decoder: Poison,
  pass: ["text/*", "application/json"]

:urlencoded部件将解析查询参数,:json部件将解析请求的主体。

然后,在您的路由下面,您可以从conn对象获得:params键中的键/值参数,如下所示:

代码语言:javascript
复制
%{params: params} = conn
key = Map.get(params, "key")
value = Map.get(params, "value")

另外,我应该注意到,目前最好的JSON解码器是杰森,它基本上是Poison的替代物,但速度更快。

不管怎样,阅读十六进制文件确实有助于解决这些问题,而且插件项目有很好的文档。我认为Elixir是一种很好的开始编程的语言(尽管学习面向对象的范例也很重要)。编码愉快!

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53558974

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档