Skip to content

Commit fa981f8

Browse files
authored
Update typespecs-and-behaviours.markdown
1 parent c4943c9 commit fa981f8

File tree

1 file changed

+31
-2
lines changed

1 file changed

+31
-2
lines changed

getting-started/typespecs-and-behaviours.markdown

+31-2
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ defmodule Parser do
131131
@doc """
132132
Parses a string.
133133
"""
134-
@callback parse(String.t) :: {:ok, term} | {:error, String.t}
134+
@callback parse(String.t) :: {:ok, term} | {:error, atom}
135135

136136
@doc """
137137
Lists all supported file extensions.
@@ -142,7 +142,36 @@ end
142142

143143
Modules adopting the `Parser` behaviour will have to implement all the functions defined with the `@callback` attribute. As you can see, `@callback` expects a function name but also a function specification like the ones used with the `@spec` attribute we saw above. Also note that the `term` type is used to represent the parsed value. In Elixir, the `term` type is a shortcut to represent any type.
144144

145-
`@callback` was initially for callbacks only. Then the idea evolved and people started using them for contract-driven programming.
145+
Behaviours are useful because you can now pass modules around as arguments and you can then _call back_ to any of the functions specified in the behaviour. For example, we can have a function that receives a filename, several parsers, and parses it with the appropriate parser based on its extension:
146+
147+
```elixir
148+
@spec parse_path(Path.t(), [module()]) :: {:ok, term} | {:error, atom}
149+
def parse_path(filename, parsers) do
150+
with {:ok, ext} <- parse_extension(filename),
151+
{:ok, parser} <- find_parser(extension, parsers),
152+
{:ok, contents} <- File.read(filename) do
153+
parser.parse(contents)
154+
end
155+
end
156+
157+
defp parse_extension(filename) do
158+
if ext = Path.extname(filename) do
159+
{:ok, ext}
160+
else
161+
{:error, :no_extension}
162+
end
163+
end
164+
165+
defp find_parser(extension, parsers) do
166+
if parser = Enum.find(parsers, fn parser -> ext in parser.extensions() end) do
167+
{:ok, parser}
168+
else
169+
{:error, :no_matching_parser}
170+
end
171+
end
172+
```
173+
174+
Of course, you could also invoke any parser directly: `CSVParser.parse(...)`.
146175

147176
### Adopting behaviours
148177

0 commit comments

Comments
 (0)