Elm: Pass current time as an effect to Tick Action, after model update - elm

In update I would like to call my Tick Action every time Input is called.
The scenario is that a user enters a value in a text field, on update the model is updated via Input and then Tick is called and more stuff is performed on the model.
In 0.16 I could do something like this :
Input query ->
({ model | query = query }, Effects.tick Tick)
Tick clockTime ->
-- do something with clockTime
I'm not sure how to do this in 0.17.
I'm not sure if this would be a subscription and if it were, how you could go about configuring it to call Input then Action.
Any help is appreciated.

The functionality for retrieving the current time as an effect has been moved into a Task, in the Time module under Time.now.
http://package.elm-lang.org/packages/elm-lang/core/4.0.1/Time#now
You can reproduce your functionality by making the following changes:
1) Make sure there is a NoOp message available in your messages. Time.now returns a Task which we know will never fail, but we still need a failure message to hand to Task.perform
type Msg
= Input String
| Tick Time
| NoOp
2) Replace Effects.tick Tick with Time.now and Task.perform
Input query ->
( { model | query = query }
, Time.now |> Task.perform (\_ -> NoOp) Tick
)
If you don't like the NoOp message, there are other ways around it, such as using Debug.crash or performFailproof from Task.Extra (found here: http://package.elm-lang.org/packages/NoRedInk/elm-task-extra/2.0.0/Task-Extra#performFailproof)

Related

How can I use Cmd.map within a update-function using multiple arguments?

In Elm 0.19.1, I have the following Msg (among others):
type Msg
= Yay Multiselect.Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Yay sub ->
let
( subModel, subCmd, _ ) =
Multiselect.update sub model
in
( { model | multiselectC = subModel }, Cmd.map Yay subCmd )
I'm using an external library here (Multiselect: https://github.com/inkuzmin/elm-multiselect). For this Msg, I'm correctly following the examples of the library. Goal here is handling events from this library to update the Model in this module. This correctly works.
Here comes the problem. I want to add multiple arguments to this Msg. For example, let's add the Multiselect.Model to it, creating the following type:
type Msg
= Yay Multiselect.Msg Multiselect.Model
This gives an error in the Cmd.map (makes sense since it is missing de Model). I've tried updating the Cmd.map-function in the following function:
Yay sub msModel ->
let
( subModel, subCmd, _ ) =
Multiselect.update sub msModel
in
( { model | multiselectC = subModel }, Cmd.map (\cmd -> Yay cmd subModel) subCmd )
This will remove the compile errors and will work for most of the events. However, some some events (for example, removing items), the library will behave unexpectedly and different from the code before.
I've also added some logging, creating the following command line:
It looks like it is correctly updating the Model when removing an selected item. However, with the next triggered event, it looks like the function is using the old model, removing the update. This basically means that nothing happens.
How can I correctly write the Cmd.map with multiple arguments which will behave the same as the first update-function with only 1 argument?
The way you pass multiple arguments to your Msgs is correct.
But you ran into a special case, because you passed state from the view.
Most times, it is better to only pass information what is supposed to change and not both the supposed change and an old state as part of your message.
My take at an explanation:
This behavior is due to the Elm runtime and optimizations for rendering:
The DOM is only rendered between 30 and 60 times a second, and the event that is fired from the view is using an old state.
So the state of your model was updated (because it is executed as fast as possible), but then later overwritten with the old state that is still being displayed by the browser.
When looking at this image from the official documentation, you can see that not every update triggers an immediate re-rendering of the DOM, but the work is done out-of-sync:
I actually fixed this issue in my case. It turns out that this method of mapping the Cmd is correct and works. However, in my case using the external library, it turns out that my view will fire multiple messages to my update-function using the same Multiselect.Model. My update-function updates this model, but this updated model will not be used for the messages fired from the view.
What I've done to fix this is removing the Multiselect.Model argument from the Msg. I will now use other arguments (that will not be updated in between the different messages) to search for the Multiselect.Model within the model of my module. This will take much code, but it actually works.

How to make antispam function discord.py?

I need antispam function on my discord server. Please help me. I tried this:
import datetime
import time
time_window_milliseconds = 5000
max_msg_per_window = 5
author_msg_times = {}
#client.event
async def on_ready():
print('logged in as {0.user}'.format(client))
await client.change_presence(activity=discord.Activity(type=discord.ActivityType.playing,name="stack overflow"))
#client.event
async def on_message(message):
global author_msg_counts
ctx = await client.get_context(message)
author_id = ctx.author.id
# Get current epoch time in milliseconds
curr_time = datetime.datetime.now().timestamp() * 1000
# Make empty list for author id, if it does not exist
if not author_msg_times.get(author_id, False):
author_msg_times[author_id] = []
# Append the time of this message to the users list of message times
author_msg_times[author_id].append(curr_time)
# Find the beginning of our time window.
expr_time = curr_time - time_window_milliseconds
# Find message times which occurred before the start of our window
expired_msgs = [
msg_time for msg_time in author_msg_times[author_id]
if msg_time < expr_time
]
# Remove all the expired messages times from our list
for msg_time in expired_msgs:
author_msg_times[author_id].remove(msg_time)
# ^ note: we probably need to use a mutex here. Multiple threads
# might be trying to update this at the same time. Not sure though.
if len(author_msg_times[author_id]) > max_msg_per_window:
await ctx.send("Stop Spamming")
ping()
client.run(os.getenv('token'))
And it doesn't seem to work when I type the same message over and over again. Can you guys please help me? I need the good antispam function which will work inside on_message
I think the best thing you can do is to make an event on_member_join, which will be called every time user joins. Then in this event, you can make a list instead of variables that will save user id, and their current currency.
users_currency = ["user's id", "5$", "another user's id", "7$"] and so on. Next, I would recommend saving it to a text file.
Example code
global users_currency
users_currrency = []
#client.event
global users_currency
async def on_member_join(member): #on_member_join event
user = str(member.id) #gets user's id and changes it to string
users_currency.append(user) #adds user's id to your list
users_currency.append("0") #sets the currency to 0
Now if someone will join their id will appear in list and change their currency to 0.
How can you use assigned values in list
If you keep the code close to example higher then on users_currrency[0], users_currrency[2], [...]. You will get users' ids and on users_currrency[1], users_currrency[3], etc. You will get their currency. Then you can use on_message event or #client.command to make command that will look for user's id in list and change next value - their currency.
Saving it to a text file
You have to save it in a text file (Writing a list to a file with Python) and then make a function that will run at the start of the bot and read everything from the file and assign it inside your list.
Example code:
with open("users_currency.txt") as f:
rd=f.read()
changed_to_a_list=rd.split()
users_currency = changed_to_a_list

XY prob: How do I issue a bunch of msgs simultaneously?

Almost definitely an XY problem but I can't think of a concise way to phrase what I'm trying to do.
I have a textarea. Inside this text area, the user enters a comma-separated list of ID numbers. When they click "Fetch", I split their input on commas, to get multiple string values, and each of those string values gets passed to a function that makes a HTTP request to my API getting info on the item.
That's where I stumble. Right now I have these parts:
getInfo : String -> Cmd Msg, takes an ID string and ultimately fires Http.send
type Msg = Fetch String, where Fetch idStr -> (model, getInfo idStr)
I sort of want to take my textarea's input and say String.split "," |> List.map (\id -> getInfo id). Except I don't know what to do with the List Msg that would give me, I want to fire each of those msgs off, but Elm doesn't work that way?
While reading I found Cmd.batch, but there's not really any info on it in the docs so I'm not sure if that's what I want or how to use it.
Yes, Cmd.batch can batch multiple cmds into one cmd.
For example (via new message: FetchAll):
FetchAll idsStr ->
let
cmds = String.split "," idsStr |> List.map (\id -> getInfo id)
in (model, Cmd.batch cmds)
also, (model, Cmd.batch cmds) can be written as model ! cmds

How to do math operation with columns on grouped rows

I have Event model with following attributes (I quoted only problem related attributes), this model is filled periodically by API call, calling external service (Google Calendar):
colorid: number # (0-11)
event_start: datetime
event_end: datetime
I need to count duration of grouped events, grouped by colorid. I have Event instance method to calculate single event duration:
def event_duration
((event_end.to_datetime - event_start.to_datetime) * 24 * 60 ).to_i
end
Now, I need to do something like this:
event = Event.group(:colorid).sum(event_duration)
But this doesnot work for me, as long as I get error that event_duration column doesnot exists. My idea is to add one more attribute to Event model "event_duration", and count and update this attribute during Event record creation, in this case I would have column called "event_duration", and I might be ale to use sum on this attribute. But I am not sure this is good and "system solution", as long as I would like to have model data reflecting "raw" received data from API call, and do all math and statistics on the top of model data.
event_duration is instance method (not column name). error was raised because Event.sum only calculates the sum of certain column
on your case, I think it would be easier to use enumerable methods
duration_by_color_id = {}
grouped_events = Event.all.group_by(&:colorid)
grouped_events.each do |colorid, events|
duration_by_color_id[colorid] = events.collect(&:event_duration).sum
end
Source :
Enumerable's group_by
Enumerable's collect

How to define function with record argument in erlang

I want to define the structure of my function input.
This module defines my function (part of it is pseudo code):
-module(my_service).
-export([my_function/2]).
-type my_enum() :: 1 | 2 | 5.
-record(my_record, {id = null :: string(), name = null :: string(), myType = null :: my_enum()}).
-spec my_function(MyValue#my_record) -> Result#my_record.
my_function(ClientRequest, MyValue) when is_record(Entry, my_record) ->
Response = client:request(get, "http://www.example.com/api?value=" + MyValue#my_record.name),
case Response of
{ok, Status, Head, Body} ->
Result = parse_response(Body, my_record);
Error ->
Error
end.
This is how I want to call it:
-module(test1).
-import(io, [format/2]).
main(_) ->
application:start(inets),
MyTestValue = #my_record{name => "test value", myType => 2},
X = my_service:my_function(MyTestValue),
io:format("Response: ~p", [X]).
So, any idea how I can force the type of the function input to be of type my_record?
It's often handy to destructure a record directly in the function argument list, which also forces that argument to have the desired record type. For example, we can slightly modify your my_function/2 like this:
my_function(ClientRequest, #my_record{name=Name}=MyValue) ->
Response = client:request(get, "http://www.example.com/api?value=" ++ Name),
case Response of
{ok, Status, Head, Body} ->
Result = parse_response(Body, MyValue);
Error ->
Error
end.
Note how we're pattern-matching the second parameter MyValue: we're not only asserting that it's a #my_record{} instance, but we're also extracting its name field into the Name variable at the same time. This is handy because we use Name on the first line of the function body. Note that we also keep the parameter name MyValue because we pass it to parse_response/2 within the function body. If we didn't need it, we could instead write the function head like this:
my_function(ClientRequest, #my_record{name=Name}) ->
This would still have the desired effect of forcing the second argument to be of type #my_record{}, and would also still extract Name. If desired, you could extract other fields in a similar manner:
my_function(ClientRequest, #my_record{name=Name, id=Id}) ->
Both Name and Id are then available for use within the function body.
One other thing: don't use the atom null as a default for your record fields. The default for Erlang record fields is the atom undefined, so you should just use that. This is preferable to defining your own concept of null in the language.