Converting Python example into Elixir

Hi Everyone,

I am sure this has been asked hundreds of times, but i couldn’t find a simple toy example which demonstrated what i wanted to understand. To be clear this is more about coming to a functional paradigm from Python, JS/TS, etc.

When learning languages and frameworks i normally create a small contrived example for a Lirabry where you can add books, withdraw them, and so on. Phase 1 of this example is just adding books, in Python this is a super simple task and fits neatly into a class.


from typing import List


class Library:
    name: str
    books: List

    def __init__(self, *, name: str, books: List):
        self.name = name
        self.books = books

    def add_books(self, *, books: List):
        self.books = [*self.books, *books]


library = Library(name="My Library", books=[])

print(library.__dict__)

library.add_books(books=["My Novel", "Some Book"])

print(library.__dict__)

library.add_books(books=["Moby Dick"])

print(library.__dict__)

I have not ported this straight to Elixir but rather i am trying to do this same task in Elixir, this is what i have currently (ignore my liberal use of pipe, it’s one of the things i really like about Elixir):


defmodule Library do
  defstruct [:name, :books]
end

defmodule Library.Inventory do
  def add_books(library, books) do
    updated_stock = [books | library.books] |> List.flatten
    %{library | books: updated_stock }
  end
end

defmodule Main do
  def main do
    library = %Library{name: "My Library", books: ["Some Novel"]}
    IO.inspect(library) # ["Some Novel"]
    library = library |> Library.Inventory.add_books(["Moby Dick", "My Novel"])
    IO.inspect(library) # ["Moby Dick", "My Novel", "Some Novel"]
  end
end

Main.main()

My questions are:

  • Is this functional? I know its a vague question, what i mean more is am i along the right track for a functional approach to this problem?
  • How can my Elixir code be improved? Are there any glaring mistakes that i as a newbie have completely missed
  • Do you know of any resources about building small practical examples such as this one?

Thanks a bunch!

For good resources to get started, please look at this post

I would try to group them into a single module, unless working with a db schema, consider Elixir Modules like a vague example of a class in OOPS.

There are no shortcuts here, it’s by practice with exercism or some small exercises. There are no mistakes in learning, it’s just trial and error. So don’t worry about getting it right the first time. Start small and build your way up

1 Like

This works, but it’s more idiomatic (and IIRC slightly more efficient) to write:

updated_stock = books ++ library.books

docs for Kernel#++/2

1 Like

++ is slow for large lists. GitHub - devonestes/fast-elixir: Writing Fast Elixir -- Collect Common Elixir idioms.

For one shot concatenation they are roughly the same: all O(n)

2 Likes

That benchmark is not very helpful.

If you were adding 1000 lists together, you would use List.foldr/3 and use ++. In the benchmark it just does acc ++ list which is obviously slow as the more the accumulator is growing, the more it takes time to concatenate it with the new list.

1 Like

functional approach would be to create a function that adds only 1 book at the beginning of the list and return the list, then you can use it in enum reduce
Enum.reduce(books, library_books, fn book, library -> add_book(library, book) end)
then update the map

It’s all about who ++ who. left ++ right is O(length(left)). As long as left is guaranteed to be short, it’s OK to use ++.

1 Like