【问题标题】:Bad Record when calling list:keyfind() in erlang在erlang中调用list:keyfind()时出现错误记录
【发布时间】:2020-02-16 01:06:37
【问题描述】:

我是 erlang 新手,我的一个模块中的记录出现错误。我在 shipping_state 中模拟船只,我想创建一个简单的函数,根据它的 ID 打印某艘船的船 ID、名称和容器盖。我使用了 list:keyfind,因为我相信它会有所帮助,但也许我没有正确使用它。我有一个包含记录声明的 .hrl 文件,以及一个包含 #shipping_state 的函数和初始化的 .erl 文件。

shipping.erl:

-module(shipping).
-compile(export_all).
-include_lib("./shipping.hrl").

get_ship(Shipping_State, Ship_ID) ->
{id, name, containercap} = list:keyfind(Ship_ID, 1, Shipping_State#shipping_state.ships).

shipping.hrl:

 -record(ship, {id, name, container_cap}).
 -record(container, {id, weight}).
 -record(shipping_state, 
    {
      ships = [],
      containers = [],
      ports = [],
      ship_locations = [],
      ship_inventory = maps:new(),
      port_inventory = maps:new()
     }
   ).
 -record(port, {id, name, docks = [], container_cap}).

结果:

 shipping:get_ship(shipping:init(),1).
 ** exception error: {badrecord,shipping_state}
 in function  shipping:get_ship/2 (shipping.erl, line 18)

我想说 keyfind 应该可以工作,也许当我创建元组 {id, name, containercap} 时,那里的语法有问题,但如果我需要完全重新考虑我将如何去做这个问题,任何帮助将不胜感激。

编辑, 我已经修改了我的代码以遵循 Alexey 的建议,但是,似乎仍然存在相同的错误。有什么进一步的见解吗?

 get_ship(Shipping_State, Ship_ID) ->
     {ship, Id, Name, Containercap} = list:keyfind(Ship_ID, 2, 
 Shipping_State#shipping_state.ships),
     io:format("id = ~w, name = ~s, container cap = ~w",[Id, Name, Containercap]).

【问题讨论】:

    标签: erlang


    【解决方案1】:
    1. Internal Representation of Records#ship{id=1,name="Santa Maria",container_cap=20}变成{ship, 1, "Santa Maria", 20},所以id是第二个元素,而不是第一个。

    2. {id, name, containercap} = ...
      

      应该是

      #ship{id=Id, ...} = ...
      

      {ship, Id, Name, Containercap} = ...
      

      只有当keyfind 返回一个包含 3 个原子的元组时,您当前的代码才会成功。

    3. 错误{badrecord,shipping_state} 告诉您get_ship 的代码期望它的第一个参数是#shipping_state,但您传递了{ok, #shipping_state{...}}init 的结果)。

    【讨论】:

    • 这很有意义,我修改了我的代码以更好地反映这一点,更改 list:keyfind() 以搜索第二个索引并更改元组。但是,它似乎仍然无法正常工作。你有什么进一步的见解吗?我收到了同样的错误。
    • 实际上,我没有注意到错误消息。问题发生在之前调用keyfind(见编辑)。
    • 我喜欢说:动态语言意味着您需要更多地考虑类型,而不是更少!
    • 是的,就是这样,我只是更改了 init() 返回的内容,现在可以使用了。谢谢!
    【解决方案2】:

    记录被添加到 Erlang 语言中是因为按数字处理元组字段很容易出错,尤其是在开发过程中更改代码以及添加、更改或删除元组字段时。不要使用数字来标识记录字段,也不要使用其底层元组表示来处理记录,因为它们都违背了记录的目的并且是不必要的。

    在您的代码中,不要使用带有lists:keyfind/3 的记录字段编号,而是使用记录名称本身。我已经修改了你的 get_ship/2 函数来做到这一点:

    get_ship(Shipping_State, Ship_ID) ->
        #ship{id=ID, name=Name, container_cap=ContainerCap} = lists:keyfind(Ship_ID, #ship.id, Shipping_State#shipping_state.ships),
        io:format("id = ~w, name = ~s, container cap = ~w~n",[ID, Name, ContainerCap]).
    

    语法#<record_name>.<record_field_name> 提供基础记录字段编号。在上面的lists:keyfind/3 调用中,#ship.id 提供了ship 记录的id 字段的字段编号。即使您将字段添加到记录中,这仍将继续正常工作,并且与原始数字不同,如果您决定在某个时候从记录中删除该字段,它将导致编译错误。

    如果您使用rr 命令将记录定义加载到shell 中,您可以看到#ship.id 返回预期的字段编号:

    1> rr("shipping.hrl").
    [container,port,ship,shipping_state]
    2> #ship.id.
    2
    

    通过对上述函数进行额外修复以正确处理返回的记录,它现在可以按预期工作,正如此 shell 会话所示:

    3> {ok, ShippingState} = shipping:init().
    {ok,{shipping_state,[{ship,1,"Santa Maria",20},
                         {ship,2,"Nina",20},
                         {ship,3,"Pinta",20},
                         {ship,4,"SS Minnow",20},
                         {ship,5,"Sir Leaks-A-Lot",20}],
                        [{container,1,200},
                         ...
    4> shipping:get_ship(ShippingState, 1).
    id = 1, name = Santa Maria, container cap = 20
    ok
    

    【讨论】:

      【解决方案3】:

      Alexey's answer 回答您的问题,尤其是第三点。我只想建议改进您的keyfind 电话。您需要将元组索引传递给它,但您可以使用记录语法来获取该索引而无需对其进行硬编码,如下所示:

       list:keyfind(Ship_ID, #ship.id, Shipping_State#shipping_state.ships),
      

      #ship.id 返回id 字段的索引,在本例中为 2。这使得代码更容易阅读 - 无需怀疑常量 2 的用途。此外,如果您出于某种原因更改了ship 记录中的字段顺序,此代码仍将编译并执行正确的操作。

      【讨论】:

      • 是的,这是一个很好的观点,我也会添加,谢谢!
      猜你喜欢
      • 2015-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-31
      • 1970-01-01
      • 2016-07-24
      • 2016-12-26
      • 2021-09-19
      相关资源
      最近更新 更多