Ookma-Kyi

Ookma-Kyi

I have the following code:

defmodule Servy.Conv do
  defstruct method: "", path: "", resp_body: "", status: nil

  def full_status(conv) do
    "#{conv.status} #{status_reason(conv.status)}"
  end

  defp status_reason(code) do
    %{
      200 => "OK",
      201 => "Created",
      401 => "Unauthorized",
      403 => "Forbidden",
      404 => "Not Found",
      500 => "Internal Server Error"
    }[code]
  end
end

I get a “could not find struct” error when I update the full_status function to def full_status(%Conv = conv) do, even though the struct is defined above. Additionally, Visual Studio Code mentions

Cyclic module usage

When I try to do this. Any ideas?

Showing Posts 1 to 10

gregvaughn

gregvaughn

The struct’s module is Servy.Conv not Conv. You have a few choices. You could use the full module name, or you could alias or you could use the compile time variable __MODULE__. For example:

def full_status(%__MODULE__{} = conf) do

I prefer the compile time variable because if you ever refactor the module and change its name, this will reflect the current name.

Ookma-Kyi

Ookma-Kyi OP

So even though you’re inside the actual struct definition, you still have to alias it, correct? I know the course mentions using alias, but that was when you wanted to use the struct in another file.

gregvaughn

gregvaughn

Yes, but it’s even more fundamental than that. The struct “name” is a module name. Even if you were not creating a struct, but in any module you cannot refer to the current module by the leaf part of its name. For example, if you were writing any function within module Foo.Bar you cannot use Bar by itself. You’d have the same 3 options I mentioned.

sodapopcan

sodapopcan

Just to put what @gregvaughn another way (which is what helped me) is that Elixir doesn’t technically have submodules. The . is a bit of an illusion and is only meaningful for constructs like alias, otherwise it’s just like any other character in a module name. Foo and Foo.Bar have absolutely no relation as far as the VM is concerned.

Ookma-Kyi

Ookma-Kyi OP

It’s weird because this is perfectly valid:

defmodule Servy.Handler do
  alias Servy.Conv

  @moduledoc "Handles HTTP requests"

  @pages_path Path.expand("pages", File.cwd!())

  @doc "Transforms the request into a response."
  def handle(request) do
    request
    |> route
    |> format_response
  end

  def route(%Conv{method: "GET", path: "/wildthings"} = conv) do
    %{conv | status: 200, resp_body: "Bears, Lions, Tigers"}
  end

  def route(%Conv{method: "GET", path: "/pages/" <> file} = conv) do
    Path.expand("../../pages", __DIR__)
    |> Path.join(file <> ".html")
    |> File.read()
    |> handle_file(conv)
  end

  def route(%Conv{method: "GET", path: "/about"} = conv) do
    Path.expand("../../pages", __DIR__)
    |> Path.join("about.html")
    |> File.read()
    |> handle_file(conv)
  end

  def route(%Conv{path: path} = conv) do
    %{conv | status: 404, resp_body: "No #{path} here"}
  end

  def format_response(%Conv{} = conv) do
    # TODO: Use values in the map to create an HTTP response string:
    """
    HTTP/1.1 #{Conv.full_status(%Conv{} = conv)}
    Content-Type: text/html
    Content-Length: #{byte_size(conv.resp_body)}

    #{conv.resp_body}
    """
  end
end

request = """
GET /wildthings HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*

"""

response = Servy.Handler.handle(request)

IO.puts(response)
sodapopcan

sodapopcan

You have alias Servy.Conv at the top of module, unless you’re talking about something else.

Ookma-Kyi

Ookma-Kyi OP

I mean that functions inside the module are fine, but structs inside the module still need to be aliased. Just something that I have to get used to, I guess.

sodapopcan

sodapopcan

That goes back to what Greg was saying that structs are named after the module itself. This is no different than if you want to use a module within itself: you either have to use __MODULE__ or you can type the full name verbatim.

As I said in my previous answer, Foo.Bar and Foo are completely different modules. It sounds like you are still thinking of it as Bar is a module within Foo. This is not the case, they are totally independent. Just think of . as an underscore. The . between the module name and the function, however, is an operator, though. It’s separating module and function name.

Sorry for the all the edits but I perhaps it’s clearer to say that the Foo in Foo and the Foo in Foo.Bar also have absolutely no relation.

Ookma-Kyi

Ookma-Kyi OP

Cool, thanks everyone. Sorry about the dumb questions. I am still learning Elixir, and slowly but surely I will get there.

garrison

garrison

I agree this behavior is confusing; I also think it would be more intuitive to alias the current module into itself. I can’t explain exactly why, it’s just less surprising for some reason.

With that said, obviously there is no changing this now, so it is what it is.

Structs are just maps with an extra __struct__ key set to the module name. Functions are imported into the scope, but structs are not. You can only alias the module name.

Note however that defining a nested module aliases that module within the parent, so you can do this:

defmodule A do
  defmodule B do
    defstruct [:foo, :bar]
  end
  def foo(%B{}), do: true
end

Even though the actual name of B is A.B. Which only makes it more confusing tbh.

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
rlipscombe
I’d like to create a file with 600 permissions (rw-------), but there’s a race condition if I create it and then change the permissions. ...
New
saleyn
Is there a way to import record definitions in the iex shell for pretty printing (similar to the rr command in the erl shell)? This woul...
New
rlipscombe
When I use Mix.install with a git dependency, using git@github.com:... how do I change the SSH identity used? I’ve got two Github accoun...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews