在 erlang 中调用 list:keyfind() 时的错误记录

Bad Record when calling list:keyfind() in erlang

我是 erlang 的新手,运行 我的一个模块中的记录出现错误。我在 shipping_state 内模拟船只,我想创建一个简单的函数,根据它的 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]).
  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的结果)。

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

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

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

记录被添加到 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.idship 记录的 id 字段提供了字段编号。即使您将字段添加到记录中,这也将继续正常工作,并且与原始数字不同,如果您决定在某个时候从记录中删除该字段,它将导致编译错误。

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

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