audun02 (17) [Avatar] Offline
#1
What do I do wrong? I paste in the code from my own editor,
it should be identical to the book listing:
defmodule TodoList do
  def new, do: HashDict.new

  def add_entry(todo_list, date, title) do
    HashDict.update(
    todo_list,
    date,
    [title],
    fn(titles) -> [title | titles] end
    )
  end

  def entries(todo_list, date) do
    HashDict.get(todo_list, date, [])
  
  end
end


Then, I run:

iex(1)> c "simple_todo.exs"
simple_todo.exs:1: warning: redefining module TodoList
[TodoList]

iex(2)> mylist = TodoList.new()
#HashDict<[]>

iex(3)> TodoList.add_entry(mylist,{15,11,06},"Dentist")
#HashDict<[{{15, 11, 6}, ["Dentist"]}]>

iex(4)> mylist
#HashDict<[]> // What? mylist is empty?

iex(5)> TodoList.entries(mylist,{15,11,06})
[] // mylist hasn't been populated !!
sjuric (86) [Avatar] Offline
#2
Your code is fine. But the problem is in your usage. When you write:

iex(3)> TodoList.add_entry(mylist,{15,11,06},"Dentist") 


You get the correct result. But you don't take it into some variable, and thus your mylist still "points" to the original data.

Keep in mind that there's no in-place data mutation in Elixir. You can't change the content of mylist. You can only invoke the function which will return the new data based on the original one. You need to take the result of the function in another variable. You can also take the result into the variable of the same name in which case the variable is "rebound" - it will point to a different data, while the original data is unaffected.

So you need to do something like:

iex(1)> defmodule ...

iex(2)> mylist = TodoList.new()
#HashDict<[]>

iex(3)> list2 = TodoList.add_entry(mylist,{15,11,06},"Dentist")
#HashDict<[{{15, 11, 6}, ["Dentist"]}]>

iex(4)> list2
#HashDict<[{{15, 11, 6}, ["Dentist"]}]>

iex(5)> mylist
#HashDict<[]>


This should prove that your data is immutable. Var list2 holds the modified version of the to-do list, but the original is not modified. As explained in section 2.4.5, as much of the data as possible will be shared between two instances.
audun02 (17) [Avatar] Offline
#3
Of course ! Shame on me, I should have figured out that!
Thanks for quick reply! (Very nice book, btw)

audun
audun02 (17) [Avatar] Offline
#4
Oh, another question: I do a small change:
def add_entry(todo_list, date, title) do
    HashDict.update(
      todo_list,
      date,
      title,                                       [b]# <- simple 'title' instead of [title][/b]
      fn(titles) -> [title | titles] end
    )
  end


If one permist entering a list of titles, the result of TodoList.entries(....) becomes ugly: not simply a list of entries, but probably also a list of simple entries mixed with lists of entries: e.g.
[["bookmaker","Vet"], "Dentist",.....]

Just a comment.
- audun
sjuric (86) [Avatar] Offline
#5
Yes, this happens because your first add_entry will insert a "plain" title (i.e. not a list). The next add_entry then builds a so called improper list, where the tail is not a list. Instead the tail will be the first entry you added, and the result can then look weird. What's worse, it can be hard to interpret such result, because you may end up with a potentially deep nested list.

This is why we use [title] in the original code. It ensures that when the first entry is inserted for some date, we immediately create a one-element list. Subsequent invocations of add_entry will just prepend the new element to the properly constructed list, so we know we always have a list of titles.

It's also worth looking how HashDict.update works. It's explained in the help for Dict.update.
audun02 (17) [Avatar] Offline
#6
I'm not sure I understand (but I still haven't read about improper lists).
But when I run the program inserting simple 'title' (not [title]), it runs like this:
iex(1)> c "simple_todo.exs"
simple_todo.exs:1: warning: redefining module TodoList
[TodoList]

iex(2)> newlis = TodoList.new()
#HashDict<[]>

iex(3)> newlis2 = TodoList.add_entry(newlis,{2015,11,05},"Doctor")
#HashDict<[{{2015, 11, 5}, ["Doctor"]}]>

iex(4)> newlis3 = TodoList.add_entry(newlis2,{2015,11,05},"Dentist")
#HashDict<[{{2015, 11, 5}, ["Dentist", "Doctor"]}]>

iex(5)> newlis3 = TodoList.add_entry(newlis3,{2015,11,05},"Vet")    
#HashDict<[{{2015, 11, 5}, ["Vet", "Dentist", "Doctor"]}]>
    
iex(6)> [head|tail] = TodoList.entries(newlis3,{2015,11,05})
["Vet", "Dentist", "Doctor"]
iex(7)> head
"Vet"

iex(8)> tail
["Dentist", "Doctor"]  # <- Both a head and a teil. Looks like a normal list to me ??
iex(9)> 
audun02 (17) [Avatar] Offline
#7
...and lastly: the list ends in [] :

iex(9)> [h|t] = ["Dentist", "Doctor"]
["Dentist", "Doctor"]

iex(10)> t
["Doctor"]

iex(11)> [h|t] = ["Doctor"]
["Doctor"]

iex(12)> t
[]                          #  <-  Last element of the list
sjuric (86) [Avatar] Offline
#8
Something is strange there. The output is definitely not expected if you use just title, instead of [title]. My guess is that the newer version of the module is somehow not loaded. I'd suggest you exit the shell, make sure to delete any .beam files and try again. Maybe by just directly copy-pasting to the shell.

Here's how it looks on my end:

iex(1)> defmodule TodoList do
          def new, do: HashDict.new
        
          def add_entry(todo_list, date, title) do
            HashDict.update(
              todo_list,
              date,
              title,
              fn(titles) -> [title | titles] end
            )
          end
        
          def entries(todo_list, date) do
            HashDict.get(todo_list, date, [])
          end
        end

iex(2)> newlis = TodoList.new
#HashDict<[]>

iex(3)> newlis2 = TodoList.add_entry(newlis,{2015,11,05},"Doctor")
#HashDict<[{{2015, 11, 5}, "Doctor"}]>

iex(4)> newlis3 = TodoList.add_entry(newlis2,{2015,11,05},"Dentist")
#HashDict<[{{2015, 11, 5}, ["Dentist" | "Doctor"]}]>


Notice the character | between two elements. This is an indication you have an improper list. The tail of the list is not the list itself. This is quite expected, since in the first update, we created a string under the given key. The next update, however, assumes the value is list, and appends with

[title | titles]


But titles is not a list itself. Now, that code won't actually break, but you'll get an improper list. This now may become a problem, because many functions won't work anymore:

iex(5)> entries = TodoList.entries(newlis3,{2015,11,05})
["Dentist" | "Doctor"]

iex(6)> length(entries)
** (ArgumentError) argument error
    :erlang.length(["Dentist" | "Doctor"])


In practice, improper lists are something you probably never need to use. I don't remember I've ever needed them.

The bottom line is to keep in mind you always construct the desired data format. In the given example, we want to keep the list of entries under the given date. Thus, by using [title] in the third argument to HashDict.update, we make sure that we'll construct the list when adding the first entry on a given date. Subsequent updates then prepend new entries with [title | titles], and that will work properly only if whatever is in titles is in fact a list.
audun02 (17) [Avatar] Offline
#9
Yes, you're right!

After deleting the source-file and the beam-file, it worked as you said.

I must have forgotten to save the 'title'-version and worked on the [title]-version unknowingly.

- audun