Search code examples
elixirphoenix-framework

Elixir Phoenix use of scope keyword and syntax question


I'm new to Elixir and Phoenix and trying to wrap my head around some of the syntax. Looking at the basic HelloWeb code section below (code from router.ex file)

  scope  "/", HelloWeb do ----(1)
    pipe_through :browser 

    get "/", PageController, :index ---(2)
  end

Looking at line (1) in the code above,

  1. Is this calling the function scope. Howcome there is no parenthesis.
  2. HelloWeb is the name of a module. What is the purpose in adding it here

Looking at line (2) in the code above,

  1. How does it find PageController. The module PageController is in is defmodule HelloWeb.PageController do. My guess is that adding HelloWeb in line (1) kind of makes it a shorten form ?
  2. What does :index mean (there is a function called index), specifically the :. Why use the :. From my limited knowledge on Elixir : signifies an atom.

Solution

  • StackOverflow may not be the best medium for these types of "YMMV" questions, but let me attempt a couple brief explanations.

    In some languages you may have heard "everything is an object." In Elixir, it's almost like "everything is a function." Not quite, but thinking that way can help explain things.

    scope isn't a function, it's a macro, but like a function, it can accept arguments. In Elixir, parentheses around arguments are optional. This can help readability -- e.g. even def used to define a function is actually a thing that accepts arguments.

    The 2nd argument (HelloWeb) allows us to omit the full module names. If you skipped that argument, you would have to list the full module names for your routes, e.g.

    get "/", HelloWeb.PageController, :index
    

    So that answers your next question: yes, providing that as an argument allows us to shorten the form.

    Next, why is the index function specified with a :? You are correct: this indicates an atom. Module and function names are stored internally as atoms. Importantly, when you want to call a function at runtime and you want the exact function to vary (i.e. be a variable), you can use Kernel.apply/3, which accepts a Module, the function name (as an atom), and a list of arguments. E.g. instead of IO.puts("Hello world"), you would call apply(IO, :puts, ["Hello world"]). This syntax is similar to other languages (e.g. something like PHP's call_user_func()). It's common to see "MFA" notation, which stands for Module Function Arguments, and the function name is always an atom.