I’m putting together a prototype that accesses the Spotify API, and searches for music. Used the sample LiveView project that Phoenix creates, as a starting point.
I was wondering how (and why) the sample code works.
defp search(query) do
if not SpotifyapiWeb.Endpoint.config(:code_reloader) do
raise "action disabled when not in development"
end
#app <- Application.started_applications()
Logger.info "In search, #{inspect(Application.started_applications())}"
for {app, desc, vsn} <- Application.started_applications(),
app = to_string(app),
String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
into: %{},
do: {app, vsn}
end
This handles autocomplete queries, but I can’t quite wrap my mind around how it calls the Hexdocs endpoint and how this works.
It doesn’t call Hexdocs - it uses the list of started applications available from the runtime (Application.started_applications/0) and if one matches it redirects the browser to Hexdocs:
I’d take a look at this article explaining Elixir’s for - there’s quite a lot going on between for and do {app,vsn} in the code sample you posted, and this article does an excellent job of explaining it: Elixir's for-loops go beyond "comprehension" | Hashrocket
a list of expressions, similar to a with, some using <- and some using =, as well as boolean expressions
an into option which specifies the shape of the result. See Enum.into and the Collectable protocol for more detail
a do expression, which is used to produce the final result.
Here are some examples.
for x <- 1..10,
do: Integer.to_string(x)
# result:
# ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
In its simplest form, a for looks a lot like Enum.map.
Just like in a with, the left-hand side of <- can fail to match - in that case, the for produces no output for that value of the enumerable:
for x when x < 6 <- 1..10,
do: Integer.to_string(x)
# result:
# ["1", "2", "3", "4", "5"]
for x when rem(x,2) == 0 <- 1..10,
do: Integer.to_string(x)
# result:
# ["2", "4", "6", "8", "10"]
Just like in a with, = can be used to bind names. Note that unlike<-, match failures will escape:
for x <- 1..10,
{:ok, y} = x,
do: Integer.to_string(x)
# result:
# ** (MatchError) no match of right hand side value: 1
Finally, there’s a shorthand for filtering values like Enum.filter; if an expression returns false or nil the element is omitted from the result. This is desired in cases like the initial example, where elements that return false when evaluating String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS") - but beware of the gotcha called out in @mindok’s link:
for person <- people,
name = person.name,
active = person.active do
"#{name} status is: #{active}"
end
# => ["John status is: true"]
for person <- people,
name = person.name do
"#{name} status is: #{person.active}"
end
# => ["John status is: true", "Patricia status is: false"]
Here, the expression active = person.active binds the name active, but also (when person.active is false) causes for to skip the element.