I'm new to erlang, and am running into an error with records in one of my modules. I'm emulating ships inside of a shipping_state, and I want to create a simple function that will print the ship id, name, and container cap of a certain ship, based on it's ID. I utilized list:keyfind, as I believe it will help, but perhaps I am not using it correctly. I have a .hrl file that contains the record declarations, and a .erl file with the function and initialization of my #shipping_state.
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}).
Result:
shipping:get_ship(shipping:init(),1).
** exception error: {badrecord,shipping_state}
in function shipping:get_ship/2 (shipping.erl, line 18)
I'd like to say that keyfind should work, and perhaps when I create the tuple {id, name, containercap}, something is wrong with the syntax there, but if I need to completely rethink how I would go about doing this problem, any assistance would be greatly appreciated.
Edit,
I've modified my code to follow Alexey's suggestions, however, there still appears to be the same error. Any further insights?
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]).
See Internal Representation of Records: #ship{id=1,name="Santa Maria",container_cap=20} becomes {ship, 1, "Santa Maria", 20}, so the id is the 2nd element, not the first one.
{id, name, containercap} = ...
should be
#ship{id=Id, ...} = ...
or
{ship, Id, Name, Containercap} = ...
Your current code would only succeed if keyfind returned a tuple of 3 atoms.
The error {badrecord,shipping_state} is telling you that the code of get_ship expects its first argument to be a #shipping_state, but you pass {ok, #shipping_state{...}} (the result of init).
Records were added to the Erlang language because dealing with tuples fields by number was error-prone, especially as code changed during development and tuple fields were added, changed, or dropped. Don't use numbers to identify record fields, and don't treat records using their underlying tuple representation, as both work against the purpose of records and are unnecessary.
In your code, rather than using record field numbers with lists:keyfind/3, use the record names themselves. I've revised your get_ship/2 function to do this:
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]).
The syntax #<record_name>.<record_field_name> provides the underlying record field number. In the lists:keyfind/3 call above, #ship.id provides the field number for the id field of the ship record. This will continue to work correctly even if you add fields to the record, and unlike a raw number it will cause a compilation error should you decide to drop that field from the record at some point.
If you load your record definitions into your shell using the rr command, you can see that #ship.id returns the expected field number:
1> rr("shipping.hrl").
[container,port,ship,shipping_state]
2> #ship.id.
2
With the additional repairs to your function above to handle the returned record correctly, it now works as expected, as this shell session shows:
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
Alexey's answer answers your question, in particular the 3rd point. I just want to suggest an improvement to your keyfind call. You need to pass the tuple index to it, but you can use record syntax to get that index without hard-coding it, like this:
list:keyfind(Ship_ID, #ship.id, Shipping_State#shipping_state.ships),
#ship.id returns the index of the id field, in this case 2. This makes it easier to read the code - no need to wonder what the constant 2 is for. Also, if for whatever reason you change the order of fields in the ship record, this code will still compile and do the right thing.
I am making a script inside TextButton script that will check if the TextBox contains any of the word or string inside the table.
text = script.Parent.Parent:WaitForChild('TextBox')
label = script.Parent.Parent:WaitForChild('TextLabel')
a = {'test1','test2','test3'}
script.Parent.MouseButton1Click:connect(function()
if string.match(text.Text, a) then
label.Text = "The word "..text.Text.." was found in the table."
else
label.Text = "The word "..text.Text.." was not found in the table."
end
end)
But it gives an error string expected, got table. from line 7 which is refering to the line if string.match....
Is there any way to get all text in the table?
What's the right way to do it?
Oh boy, there's a lot to say about this.
The error message
Yes.
No, seriously, the answer is yes. The error message is exactly right. a is a table value; you can clearly see that on the third line of code. string.match needs a string as its second argument, so it obviously crashes.
Simple solution
use a for loop and check for each string in a separately.
found = false
for index, entry in ipairs(a) do
if entry == text.Text then
found = true
end
end
if found then
... -- the rest of your code
The better* solution
In Lua, if we want to know if a single element is in a set, we usually take advantage of the fact that tables are implemented as hashmaps, meaning they are very fast when looking up keys.
For that to work, one first needs to change the way the table looks:
a = {["test1"] = true, ["test2"] = true, ["test3"] = true}
Then we can just index a with a string to find out if it is contained int eh set.
if a[text.Text] then ...
* In practice this is just as good as the first solution as long as you only have a few elements in your table. It only becomes relevant when you have a few hundred entries or your code needs to run absolutely as fast as possible.
I have a test server for gmod. I've coded a script that works excellent when I launch it, but there is a lot of downsides with it.
I've tried to code a script that will simply change the users speed if they type a command like "!speed fast", or "!speed normal". It looks like this:
table = {}
table[0]="!help"
table[1]="!speed normal"
table[2]="!speed fast"
table[3]="!speed sanic"
hook.Add("PlayerSay", "Chat", function(ply, message, teamchat)
if message == "!speed normal" then
GAMEMODE:SetPlayerSpeed(ply, 250, 500 )
elseif message == "!speed fast" then
GAMEMODE:SetPlayerSpeed(ply, 1000, 2000 )
elseif message == "!speed sanic" then
GAMEMODE:SetPlayerSpeed(ply, 10000, 20000)
elseif message == "!help" then
for key, value in pairs(table) do
PrintMessage( HUD_PRINTTALK, value)
end
end
end)
As you can see the script change the users speed if they either type "!speed normal", "!speed fast" or "!speed sanic" in chat. The script also contains a table of every command, and it will be shown if the user type "!help" in chat.
When I launch the script it works excellent, but if I try to spawn a prop after I've launched it, the prop won't spawn. Even when I spawn a prop first, then launch the script and try to "undo" the prop, the "undo" function won't work! The script makes Sandbox gamemode completely useless, because you can't even spawn props!
I've tried to search a little bit around on the internet first, but I haven't stumbled across something like this yet, so I hope someone got the solution! Please help
My guess is that this is happening because you are overwriting the global table. The table library contains helper functions for tables. Try renaming your table table to something else, like commands. I would also suggest you declare it as local commands so it does not replace any other global, so it does not interfere with anything else, like other scripts or library.
Also, as extra tips, lua tables are indexed with 1, So you could declare your renamed table as:
local commands = {
"!help",
"!speed normal",
"!speed fast",
"!speed sanic",
}
You could then iterate over it with a normal for:
for index = 1, #commands do
PrintMessage(HUD_PRINTTALK, commands[index])
end
I think this makes it a bit cleaner, in my opinion.
I would like to create a keybinding to switch focus to the master client. Profjim on this forum thread notes:
To get the master client on the current tag:
c = awful.client.getmaster()
I have tried the following, but it causes my ~/.config/rc.lua file to be ignored, which is the behavior if there is an error in the file. Does anyone know the correct syntax?
awful.key({ modkey, , "e", awful.client.getMaster()),
Note: "e" shouldn't cause any conflicts if you have the default key bindings.
Edit: Someone on /r/awesomewm knew the syntax to solve my problem:
awful.key({ modkey, }, "e", function() client.focus = awful.client.getmaster(); client.focus:raise() end),
Lets start with the syntax errors; from the documentation it seems that awful.key is a table, not a function. and it would presumably contain keys...which are hash tables, not sequences.
Finally your table syntax is wrong; a field may not be syntactically empty, it must have a listed value, even if that value is nil.
So basically you are trying to pass the wrong kind of value to something that can't be called.
As for how to do it correctly...the documentation is confusing, and apparently I'm not the only one that thinks so.
*deep breath*
okay, awful.new(...) creates key binders(?), and awful.key contains key bindings, so clearly we have to put the results of the first into the second.
the code on your link is but a pointer, and only covers focusing the window, not creating a keybinding.
It seems like you want something like this:
function do_focus()
current = client.focus
master = awful.client.getmaster()
if current then
client.focus = master
master:raise()
end
end
table.insert(awful.key, awful.new (modkey, 'e', nil, do_focus) )
Bare in mind that I have no way of testing the above code.
I am trying to insert something into cassandra 0.7.6 from Erlang R14B02 (through thrift 0.6.1)
I am doing the following:
Read record definitions
rr(cassandra_types).
Connect to cassandra
{ok, C}=thrift_client_util:new("localhost", 9160, cassandra_thrift,[{strict_read, false}, {strict_write, false}, {framed, true}]).
Try to insert a value (timestamp=1, 2=Quorum)
Reply1 = thrift_client:call(C, 'insert', ["existing_keyspace", "new_key",#columnPath{column_family = "existing_column_family", column = "existing_column"}, "new_value",1,2]).
But nr3 gives me a bad_args error (1 and 2 work perfectly). What would be the right arguments?
What API information there is for unsupported languages is largely in their Cassandra Thrift API documentation.
In Cassandra 0.7, you don't supply the keyspace for most operations, so insert just takes [Key, ColumnPath, Column, ConsistencyLevel]. You need to call set_keyspace before attempting the insert. The insert in erlang would be
Reply1 = thrift_client:call(C, 'insert',
[SomeKey,
#columnPath{column_family = "existing_column_family",
column = "existing_column"},
#column{name="existing_column",
value="new_value",timestamp=1},
?cassandra_ConsistencyLevel_QUORUM]).
Your example is missing the row key for the insert I think too.
As an aside, make sure you always update the value of C - it changes after every thrift_client call.
?cassandra_ConsistencyLevel_QUORUM is 2 from memory.
This might help, although there is no erlang-specific code: http://wiki.apache.org/cassandra/ThriftExamples