small medium large xlarge

Generic-user-small
06 Nov 2014, 19:22
seadynamic8 (7 posts)

I was trying the Excercise: OrganizingAProject-6 and I was having a lot of trouble getting the XML back in the right format, and I found it was with Enum.map

Here’s the code:

@columns [ :location, :observation_time, :temp_f, :wind_mph ]
locations = ["KYNC", "KDTO"]
locations 
  |> Enum.map &Weather.NOAAWeather.fetch(&1, @columns)
  |> IO.inspect

I would get:

["New York City, Central Park, NY", "Last Updated on Nov 4 2014, 10:51 pm EST",
 "61.0", ""]
["Denton Municipal Airport, TX", "Last Updated on Nov 4 2014, 10:53 pm CST",
 "51.0", "5.8"]

Notice, how it two separate lists, not a lists of lists, which it should be.

I ended up having to not use the pipe operation, because it didn’t properly return a list.

list = Enum.map locations, &Weather.NOAAWeather.fetch(&1, @columns)
IO.inspect list

This would result in the proper format, list of lists:

[["New York City, Central Park, NY", "Last Updated on Nov 4 2014, 11:51 pm EST",
  "61.0", "4.6"],
 ["Denton Municipal Airport, TX", "Last Updated on Nov 4 2014, 10:53 pm CST",
  "51.0", "5.8"]]

I don’t know if it is a bug in Elixir, but it caused me hours of headaches. I just assumed that using a pipe operator out of a Enum.map would result in a list, but not so. I’m not even sure what that first result is supposed to be, it’s just 2 lists, one after another, but you can’t enumerate that or do anything to it.

Hope no one runs in the same trouble. Or it could be me mistaking something, its getting late, and I’m getting tired. Anyone have any hints as to why that is the case. I can show more code if necessary.

Jarvis

Giphy_pragsmall
05 Nov 2014, 15:10
☺ ☺ (7 posts)

Jarvis,

What does Weather.NOAAWeather.fetch do? Enum.map should indeed always return a list (unless Elixir does something very unorthodox), but I think it’s very unlikely that the bug/weird behavior is because of map.

Generic-user-small
06 Nov 2014, 19:17
seadynamic8 (7 posts)

Lars,

Weather.NOAAWeather.fetch does a few things:

  • takes a location, and @columns
  • use HTTPoision to issue a get request for the XML
  • parses it with SweetXML library(taking in @columns)
  • –> converts each column item to_string(binary), since SweetXML returns a char string
  • finally returns, using another Enum.map into a list of column items, for a particular location.

Here’s the code:

defmodule Weather.NOAAWeather do

	import SweetXml

	def fetch(location, headers) do
		location
			|> weather_url
			|> HTTPoison.get!
			|> handle_response(headers)
	end

	def weather_url(location) do
		"http://w1.weather.gov/xml/current_obs/#{location}.xml"
	end

	def handle_response(response, headers) do
		case response do
			%{status_code: 200, body: body} ->
				Enum.map(headers, fn header ->
					body 
						|> xpath(~x"//#{header}/text()")
						|> to_string
				end)
			%{status_code: 404} ->
				IO.puts "Error fetching from NOAA: Not Found!"
				System.halt(2)
			{:error, %{reason: reason}} ->
				IO.puts "Error fetching from NOAA: #{reason}"
				System.halt(2)
			_ ->
				IO.puts "Error fetching from NOAA"
				System.halt(2)
		end
	end
end

To show that Weather.NOAAWeather.fetch does return a list, here’s what it shows in iex:

iex(1)> Weather.NOAAWeather.fetch(["KNYC"], [ :location, :observation_time, :temp_f, :wind_mph ])
["New York City, Central Park, NY", "Last Updated on Nov 6 2014, 12:51 pm EST",
 "50.0", "8.1"]

So I have no idea why Enum.map, using the Pipe operator, won’t pipe out a list of lists. But when patterned matched with a variable, it will.

It may not actually be Enum.map, but the Pipe operation that is not working as it should, since assigning (pattern matching) to a variable works.

Any ideas?

Giphy_pragsmall
07 Nov 2014, 00:56
☺ ☺ (7 posts)

I tried mocking your code like this to see if I could reproduce:

defmodule Test do
  @columns [ :location, :observation_time, :temp_f, :wind_mph ]
  def mockfetch(_location, _headers) do
    [ "New York City, Central Park, NY"
    , "Last Updated on Nov 6 2014, 12:51 pm EST"
    , "50.0"
    , "8.1"
    ]
  end

  def run() do
    locations = ["KYNC", "KDTO"]
    locations
    |> Enum.map &mockfetch(&1, @columns)
    |> IO.inspect
  end
end

The result was that IO.inspect shows the same single level list that you get, but iex returns a list of lists. Note that the output from IO.inspect doesn’t separate the elements by a comma and shows two separate lists, so it’s not really a single list at all.

iex> Test.run
# IO.inspect value
["New York City, Central Park, NY", "Last Updated on Nov 6 2014, 12:51 pm EST",
"50.0", "8.1"] # note: no comma
["New York City, Central Park, NY", "Last Updated on Nov 6 2014, 12:51 pm EST",
"50.0", "8.1"]
# Returned value
[["New York City, Central Park, NY", "Last Updated on Nov 6 2014, 12:51 pm EST",
  "50.0", "8.1"],
["New York City, Central Park, NY", "Last Updated on Nov 6 2014, 12:51 pm EST",
  "50.0", "8.1"]]

Seeing as your first result is identical to the first output here, I’m guessing that it’s simply IO.inspect pretty-printing the outermost list by removing [, ] and the comma.

Why on Earth IO.inspect flattens the list in your code but not when called directly with a list variable as an argument, however, beats me.

Generic-user-small
07 Nov 2014, 01:53
seadynamic8 (7 posts)

Lars,

EDIT: I figured it out.

I had to really break it down to something simple like this (not exactly):

def run() do
  locations = [1, 2]
  Enum.map locations, fn _ -> [	1, 2, 3, 4 ] end
  |> IO.inspect
end

Anyways, what I was missing was the parenthesis around the Enum.map arguments.

locations
  |> Enum.map( &Weather.NOAAWeather.fetch(&1, @columns) )
  |> IO.inspect

The book earlier has said that the pipe operator can get confused sometimes without the parenthesis. I can see why now. Phew…

Thanks for the help.


Thanks for trying. It seems like the same behavior for me.

I don’t think it is the IO.inspect that is at fault, but somewhere between the Enum.map and the Pipe operator.

Since, using the Pipe, I try to feed it into another function which, then complains it is not Enumerable.

I only used IO.inspect to see what was happening. And therefore, it shows that it is not a list of lists, but just two lists, not comma-seperated.

I think the returned valued you have above, which is a list of lists, is the same thing as when I assign the result to a variable and then use that for IO.inspect (or for my other function)

Anyways, I know to lookout for that if somehow later it doesn’t work the way I want it to. Hope it helps others, but there probably is bug somewhere. Unless of course, we don’t really understand how Enum.map or the Pipe operator really work.

Jarvis

You must be logged in to comment