ClojureScript - construct url conditionally in go block - url

I am using cemerick/url to construct my URL for an ajax request.
However, some of the parameters of the query are coming from an asynchronous native callback. So I put everything in a go block, like the following :
(defn myFn [options]
(go (let [options (cond-> options
;; the asynchronous call
(= (:loc options) "getgeo") (assoc :loc (aget (<! (!geolocation)) "coords")))
constructing the url
url (-> "http://example.com"
(assoc-in [:query :param] "a param")
;; How do I not associate those if they don't exist ?
;; I tried something along the lines of this, but it obviously doesn't work.
;; (cond-> (and
;; (-> options :loc :latitude not-nil?)
;; (-> options :loc :latitude not-nil?))
;; (do
;; ))
;; these will fail if there is no "latitude" or "longitude" in options
(assoc-in [:query :lat] (aget (:loc options) "latitude"))
(assoc-in [:query :lng] (aget (:loc options) "longitude"))
;; url function from https://github.com/cemerick/url
(url "/subscribe")
str)])))
I would like to be able to be able to pass either {:loc "local} or {:loc {:latitude 12 :longitude 34}} or {} as a parameter to my function.
I feel that I am not using the right structure already.
How I should construct this url ?

If I understood your question right, you require code, which looks like that:
;; helper function
(defn add-loc-to-url-as [url loc k as-k]
(if-let [v (k loc)]
(assoc-in url [:query as-k] v)
url))
;; define function, that process options and extends URL
(defn add-location [url options]
(if-let [location (:loc options)]
(if (string? location)
;;; it expects string, for {:loc "San Francisco"}
(assoc-in url [:query :location] location)
;;; or map, for {:loc {:latitude 12.123, :longitude 34.33}}
(-> url
(add-loc-to-url-as location :latitude :lat)
(add-loc-to-url-as location :longitude :lng)))
url))
;; now you can use it in your block
;; ...
the-url (-> (url "http://example.com" "subscribe")
(assoc-in [:query :param] "a param")
(add-location options))

Related

Elixir: pattern matching two same arguments to a function

I'm iterating through a list of strings, and I want to return the contents of a string if the beginning of it matches the provided string.
e.g.
strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
IO.puts fn(strings, "GITHUB") // => "github.com"
This is what I thinking so far:
def get_tag_value([ << tag_name, ": ", tag_value::binary >> | rest ], tag_name), do: tag_value
def get_tag_value([ _ | rest], tag_name), do: get_tag_value(rest, tag_name)
def get_tag_value([], tag_name), do: ""
But I get this:
** (CompileError) lib/file.ex:31: a binary field without size is only allowed at the end of a binary pattern and never allowed in binary generators
Which makes sense, but then I'm not quite sure how to go about doing this. How would I match a substring to a different variable provided as an argument?
Here's how I'd do this making most use of pattern matching and no call to String.starts_with? or String.split:
defmodule A do
def find(strings, string) do
size = byte_size(string)
Enum.find_value strings, fn
<<^string::binary-size(size), ":", rest::binary>> -> rest
_ -> nil
end
end
end
strings = ["GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com"]
IO.inspect A.find(strings, "GITHUB")
IO.inspect A.find(strings, "STACKOVERFLOW")
IO.inspect A.find(strings, "GIT")
IO.inspect A.find(strings, "FOO")
Output:
"github.com"
"stackoverflow.com"
nil
nil
There are many ways to skin this cat.
For example:
def get_tag_value(tag, strings) do
strings
|> Enum.find("", &String.starts_with?(&1, tag <> ":"))
|> String.split(":", parts: 2)
|> Enum.at(1, "")
end
or if you still wanted to explicitly use recursion:
def get_tag_value(_tag, []), do: ""
def get_tag_value(tag, [str | rest]) do
if String.starts_with?(str, tag <> ":") do
String.split(str, ":", parts: 2) |> Enum.at(1, "")
else
get_tag_value(tag, rest)
end
end
Are just two of many possible ways.
However, you won't be able to pattern match the string in the function head without knowing it (or at least the length) beforehand.
iex(1)> strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
iex(2)> Enum.filter(strings, fn(s) -> String.starts_with?(s, "GITHUB") end)
iex(3)> |> Enum.map(fn(s) -> [_, part_2] = String.split(s, ":"); part_2 end)
# => ["github.com"]
In Enum.filter/2 I select all strings they start with "GITHUB" and I get a new List. Enum.map/2 iterates through the new List and splits each string at the colon to return the second part only. Result is a List with all parts after the colon, where the original string starts with "GITHUB".
Be aware, that If there's an item like "GITHUBgithub.com" without colon, you get a MatchError. To avoid this either use String.starts_with?(s, "GITHUB:") to filter the right strings or avoid the pattern matching like I did in Enum.map/2 or use pattern matching for an empty list like #ryanwinchester did it.
You can use a combination of Enum.map and Enum.filter to get the matching pairs you're looking for:
def get_tag_value(tag_name, tags) do
tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
end
And in the end you can either call List.last/1 again to either get an empty list (no match found) or the tag value.
Alternatively you can use a case statement to return a different kind of result, like a :nomatch atom:
def get_tag_value(tag_name, tags) do
matches = tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
case matches do
[] -> :nomatch
[_, tag_value] -> tag_value
end
end
This would be my take in Erlang:
get_tag_value(Tag, Strings) ->
L = size(Tag),
[First | _] = [Val || <<Tag:L/binary, $:, Val/binary>> <- Strings]
First.
The same in Elixir (there are probably more idiomatic ways of writing it, tho):
def gtv(tag, strings) do
l = :erlang.size(tag)
[first | _ ] =
for << t :: binary - size(l), ":", value :: binary >> <- strings,
t == tag,
do: value
first
end

How is a chat input field defined in reagent?

Say that you have a text field which is the input of a chat program written in cljs with reagent. It could look something like this:
(defn chat-input []
(let [written-text (atom "")]
(fn []
[:textarea
{:value #written-text
:on-change #(reset! written-text (-> % .-target .-value))}])))
Now the easy way to implement sending a message is to add a send button. But there's one interaction that's so integral to chat that you can't really be without it: That enter or shift-enter sends the message. But I can't figure out how to implement it.
My first try was to simply add a :on-key-press event handler to send the message and reset the state to "". This solution was inspired by How to detect enter key press in reagent.
(defn chat-input []
(let [written-text (atom "")]
(fn []
[:textarea
{:value #written-text
:on-change #(reset! written-text (-> % .-target .-value))
:on-key-press (fn [e]
(let [enter 13]
(println "Key press" (.-charCode e))
(if (= (.-charCode e) enter)
(reset! written-text "")
(println "Not enter."))))}])))
The problem being that the call to (reset! written-text "") in :on-key-press has no effect, probably because it's overridden by the :on-change event handler.
So do you have any ideas on how to implement this functionality? If so, please do share!
you were on the right track, but forgot about the js event model: in your case both onChange and onKeyPress are triggered, because the target is a textarea where enter key changes the input. So in js onKeyPress is triggered first, and then it triggers onChange if the key would change something. What you need is to disable this default behavior of keyPress with preventDefault:
(defn chat-input []
(let [written-text (atom "")]
(fn []
[:textarea
{:value #written-text
:on-change #(reset! written-text (.. % -target -value))
:on-key-press (fn [e]
(when (= (.-charCode e) 13)
(.preventDefault e)
(reset! written-text "")))}])))
that should fix the problem.
Here's lot more advanced solution that mccraigmccraig on the clojurians slack so kindly allowed me to share with you. It expands the height of the textarea as the contents of the input gets larger which emulates how the chat input works in slack.
But the important part for this question is that it's :on-key-press contains a (.preventDefault e).
(defn update-rows
[row-count-atom max-rows dom-node value]
(let [field-height (.-clientHeight dom-node)
content-height (.-scrollHeight dom-node)]
(cond
(and (not-empty value)
(> content-height field-height)
(< #row-count-atom max-rows))
(swap! row-count-atom inc)
(empty? value)
(reset! row-count-atom 1))))
(defn expanding-textarea
"a textarea which expands up to max-rows as it's content expands"
[{:keys [max-rows] :as opts}]
(let [dom-node (atom nil)
row-count (atom 1)
written-text (atom "")
enter-keycode 13]
(reagent/create-class
{:display-name "expanding-textarea"
:component-did-mount
(fn [ref]
(reset! dom-node (reagent/dom-node ref))
(update-rows row-count max-rows #dom-node #written-text))
:component-did-update
(fn []
(update-rows row-count max-rows #dom-node #written-text))
:reagent-render
(fn [{:keys [on-change-fn] :as opts}]
(let [opts (dissoc opts :max-rows)]
[:textarea
(merge opts
{:rows #row-count
:value #written-text
:on-change (fn [e]
(reset! written-text (-> e .-target .-value)))
:on-key-down (fn [e]
(let [key-code (.-keyCode e)]
(when (and (= enter-keycode key-code)
(not (.-shiftKey e))
(not (.-altKey e))
(not (.-ctrlKey e))
(not (.-metaKey e)))
(do
(.preventDefault e)
(send-chat! #written-text)
(reset! written-text "")))))})]))})))

Can't get the post in LISP hunchentoot

I try to implement a simple post example based on Hunchentoot.
Here is the code:
(define-easy-handler (test :uri "/test") ()
(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html
(:body
(:h1 "Test")
(:form :action "/test2" :method "post" :id "addform"
(:input :type "text" :name "name" :class "txt")
(:input :type "submit" :class "btn" :value "Submit"))))))
(define-easy-handler (test2 :uri "/test2") (name)
(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html
(:body
(:h1 name)))))
I can correctly connect to http://127.0.0.1:8080/test and see the text input form. But when I submit the text, I get a blank page where I expected a page with the title given in text input.
Not sure what is wrong, can anyone advice?
Change your handler to this
(define-easy-handler (test2 :uri "/test2") (name)
(with-html-output-to-string (*standard-output* nil :prologue t :indent t)
(:html
(:body
(:h1 (str name))))))
Then it should work. Read the cl-who documentation.
Especially the information on local macros.
I am including the relevant documentation here.
A form which is neither a string nor a keyword nor a list beginning with a keyword will be left as is except for the following local macros:
Forms that look like (str form) will be substituted with
(let ((result form)) (when result (princ result s)))
(loop for i below 10 do (str i)) =>
(loop for i below 10 do
(let ((#:result i))
(when #:result (princ #:result *standard-output*))))
Forms that look like (fmt form*) will be substituted with
(format s form*)
(loop for i below 10 do (fmt "~R" i)) => (loop for i below 10 do (format s "~R" i))
Forms that look like (esc form) will be substituted with
(let ((result form)) (when result (write-string (escape-string result s))))
If a form looks like (htm form*) then each of the forms will be subject to the transformation rules we're just describing, i.e. this is the body is wrapped with another invocation of WITH-HTML-OUTPUT.
(loop for i below 100 do (htm (:b "foo") :br))
=> (loop for i below 100 do (progn (write-string "<b>foo</b><br />" s)))

An element in a tuple or not in erlang?

I'd like to know if there is a function in Erlang can help me know whether an element is in a tuple or not. Like sets:is_element/2.
Tuple = {aaa,bbb,ccc}.
is_element_of_tuple(Tuple, aaa) % => true
is_element_of_tuple(Tuple, ddd) % => false
You can always transform the tuple to a list using tuple_to_list/1:
is_element_of_tuple(Tuple, Element) ->
lists:member(Element, tuple_to_list(Tuple)).
The simple answer is: no there is no function to do this. You have to write your own loop which traverses all the elements of a tuple until it either finds or does not find it. You an either convert the tuple to a list as above or write your own loop, something like:
is_element_of_tuple(E, Tuple) ->
is_element_of_tuple(E, Tuple, 1, tuple_size(Tuple)).
is_element_of_tuple(E, T, I, S) when I =< S ->
case element(I, T) of
E -> true;
_ -> is_element_of_tuple(E, T, I+1, S)
end;
is_element_of_tuple(_, _, _, _) -> false. %Done all the elements
Using a case and matching in this way means we check for exact equality, and it is probably a little faster than using =:= and checking if that returns true or false.

Matching tuples with don't-care variables in Erlang

I am looking for a way to find tuples in a list in Erlang using a partial tuple, similarly to functors matching in Prolog. For example, I would like to following code to return true:
member({pos, _, _}, [..., {pos, 1, 2}, ...])
This code does not work right away because of the following error:
variable '_' is unbound
Is there a brief way to achieve the same effect?
For simple cases it's better to use already mentioned lists:keymember/3. But if you really need member function you can implement it yourself like this:
member(_, []) ->
false;
member(Pred, [E | List]) ->
case Pred(E) of
true ->
true;
false ->
member(Pred, List)
end.
Example:
>>> member(fun ({pos, _, 2}) -> true; (_) -> false end, [..., {pos, 1, 2}, ...]).
Use lists:keymember/3 instead.
You can do it with a macro using a list comprehension:
-define(member(A,B), length([0 || A <- B])>0).
?member({pos, _, _}, [{width, 17, 42}, {pos, 1, 2}, totally_irrelevant]).
It is not very efficient (it runs through the whole list) but it is the closest I can think to the original syntax.
If you want to actually extract the elements that match you just remove 'length' and add a variable:
-define(filter(A,B), [_E || A =_E <- B]).
You could do it using list comprehension:
Matches = [ Match || {Prefix, _, _} = Match <- ZeList, Prefix == pos].
Another possibility would be to do what match specs do and use the atom '_' instead of a raw _. Then, you could write a function similar to the following:
member(X, List) when is_tuple(X), is_list(List) ->
member2(X, List).
% non-exported helper functions:
member2(_, []) ->
false;
member2(X, [H|T]) when not is_tuple(H); size(X) =/= size(H) ->
member2(X, T);
member2(X, [H|T]) ->
case is_match(tuple_to_list(X), tuple_to_list(H)) of
true -> true;
false -> member2(X, T)
end.
is_match([], []) ->
true;
is_match(['_'|T1], [_|T2]) ->
is_match(T1, T2);
is_match([H|T1], [H|T2]) ->
is_match(T1, T2);
is_match(_, _) ->
false.
Then, your call would now be:
member({pos, '_', '_'}, [..., {pos, 1, 2}, ...])
This wouldn't let you match patterns like {A, A, '_'} (checking where the first two elements are identical), but if you don't need variables this should work.
You could also extend it to use variables using a similar syntax to match specs ('$1', '$2', etc) with a bit more work -- add a third parameter to is_match with the variable bindings you've seen so far, then write function clauses for them similar to the clause for '_'.
Granted, this won't be the fastest method. With the caveat that I haven't actually measured, I expect using the pattern matching in the language using a fun will give much better performance, although it does make the call site a bit more verbose. It's a trade-off you'll have to consider.
May use ets:match:
6> ets:match(T, '$1'). % Matches every object in the table
[[{rufsen,dog,7}],[{brunte,horse,5}],[{ludde,dog,5}]]
7> ets:match(T, {'_',dog,'$1'}).
[[7],[5]]
8> ets:match(T, {'_',cow,'$1'}).
[]

Resources