Powered by AppSignal & Oban Pro

Typespec Drills

exercises/typespec_drills.livemd

Typespec Drills

Mix.install([
  {:jason, "~> 1.4"},
  {:kino, "~> 0.8.0", override: true},
  {:youtube, github: "brooklinjazz/youtube"},
  {:hidden_cell, github: "brooklinjazz/hidden_cell"}
])

Navigation

Return Home Report An Issue

Typespec Drills

Drills help you develop familiarity and muscle memory with syntax through repeated exercises. Unlike usual problems, Drills are not intended to develop problem solving skills, they are purely for developing comfort and speed.

This set of drills is for Typespecs. Follow the instructions for each drill and complete them as quickly as you can.

Review

You can define a function spec using the @spec module attribute. Each @spec includes the function parameter types, and the return value type.

@spec add(integer(), integer()) :: integer()
def add(int1, int2) do
  int1 + int2
end

You can define custom types using the @type module attribute. These custom types can be used inside and outside of the current module.

@type user :: %{
  name: string(),
  age: integer()
}

You can include multiple types using the | operator. This works for functions using @spec and in custom types.

@spec double(integer() | nil)

Function Specs

Add function @specs based on the function parameter names and return value.

Example Solution

defmodule FunctionSpecs do
  @spec do_nothing :: nil
  def do_nothing do
    nil
  end

  @spec accept_and_return_anything(any()) :: any()
  def accept_and_return_anything(anything) do
    anything
  end

  @spec double(float()) :: float()
  def double(float) when is_float(float) do
    float * 2.0
  end

  @spec double(integer()) :: integer()
  def double(integer) when is_integer(integer) do
    integer * 2
  end

  @spec double(number()) :: number()
  def double(number) do
    number * 2
  end

  @spec add(integer(), integer()) :: integer()
  def add(integer1, integer2) do
    integer1 + integer2
  end

  @spec multiply(integer(), integer()) :: integer()
  def multiply(integer1, integer2) do
    integer1 * integer2
  end

  @spec divide(integer(), integer()) :: float()
  def divide(integer1, integer2) do
    integer1 / integer2
  end

  @spec rounded_divide(integer(), integer()) :: integer()
  def rounded_divide(integer1, integer2) do
    div(integer1, integer2)
  end

  @spec concat(String.t(), String.t()) :: String.t()
  def concat(string1, string2) do
    string1 <> string2
  end

  @spec to_string(integer()) :: String.t()
  def to_string(integer) do
    Integer.to_string(integer)
  end

  @spec merge(map(), map()) :: map()
  def merge(map1, map2) do
    Map.merge(map1, map2)
  end

  @spec get_or_empty(Keyword.t(), atom()) :: any()
  def get_or_empty(keyword_list, atom_key) do
    Keyword.get(keyword_list, atom_key, "")
  end

  @spec split_and_lowercase(String.t()) :: [String.t()]
  def split_and_lowercase(string) do
    string
    |> String.downcase()
    |> String.split("", trim: true)
  end

  @spec string_to_int(String.t()) :: integer()
  def string_to_int(string) do
    String.to_integer(string)
  end

  @spec integers_to_strings([integer()]) :: [String.t()]
  def integers_to_strings(integers) do
    Enum.map(integers, fn int -> Integer.to_string(int) end)
  end
  
  @spec one_to_two(1) :: 2
  def one_to_two(1) do
    2
  end
end
defmodule FunctionSpecs do
  def do_nothing do
    nil
  end

  def accept_and_return_anything(anything) do
    anything
  end

  def double(float) when is_float(float) do
    float * 2.0
  end

  def double(integer) when is_integer(integer) do
    integer * 2
  end

  def double(number) do
    number * 2
  end

  def add(integer1, integer2) do
    integer1 + integer2
  end

  def multiply(integer1, integer2) do
    integer1 * integer2
  end

  def divide(integer1, integer2) do
    integer1 / integer2
  end

  def rounded_divide(integer1, integer2) do
    div(integer1, integer2)
  end

  def concat(string1, string2) do
    string1 <> string2
  end

  def to_string(integer) do
    Integer.to_string(integer)
  end

  def merge(map1, map2) do
    Map.merge(map1, map2)
  end

  def get_or_empty(keyword_list, atom_key) do
    Keyword.get(keyword_list, atom_key, "")
  end

  def split_and_lowercase(string) do
    string
    |> String.downcase()
    |> String.split("", trim: true)
  end

  def string_to_int(string) do
    String.to_integer(string)
  end

  def integers_to_strings(integers) do
    Enum.map(integers, fn int -> Integer.to_string(int) end)
  end

  def one_to_two(1) do
    2
  end
end

Custom Types

Implement the following custom @types in the module below based on the comment describing them.

Example Solution

defmodule CustomTypes do
  # a string or number
  @type unparsed_number :: String.t() | number()
  # a list of strings
  @type strings :: [String.t()]
  # a map with :title (string) and :content (string) keys. 
  @type book :: %{title: String.t(), content: String.t()}
  # A map with :name (string) and `:books` (a list of books) keys.
  @type author :: %{name: String.t(), books: [book()]}
end
defmodule CustomTypes do
  # a string or number
  @type unparsed_number
  # a list of strings
  @type strings
  # a map with :title (string) and :content (string) keys. 
  @type book
  # A map with :name (string) and `:books` (a list of books) keys.
  @type author
end

Mark As Completed

file_name = Path.basename(Regex.replace(~r/#.+/, __ENV__.file, ""), ".livemd")

save_name =
  case Path.basename(__DIR__) do
    "reading" -> "typespec_drills_reading"
    "exercises" -> "typespec_drills_exercise"
  end

progress_path = __DIR__ <> "/../progress.json"
existing_progress = File.read!(progress_path) |> Jason.decode!()

default = Map.get(existing_progress, save_name, false)

form =
  Kino.Control.form(
    [
      completed: input = Kino.Input.checkbox("Mark As Completed", default: default)
    ],
    report_changes: true
  )

Task.async(fn ->
  for %{data: %{completed: completed}} <- Kino.Control.stream(form) do
    File.write!(
      progress_path,
      Jason.encode!(Map.put(existing_progress, save_name, completed), pretty: true)
    )
  end
end)

form

Commit Your Progress

Run the following in your command line from the curriculum folder to track and save your progress in a Git commit. Ensure that you do not already have undesired or unrelated changes by running git status or by checking the source control tab in Visual Studio Code.

$ git checkout -b typespec-drills-exercise
$ git add .
$ git commit -m "finish typespec drills exercise"
$ git push origin typespec-drills-exercise

Create a pull request from your typespec-drills-exercise branch to your solutions branch. Please do not create a pull request to the DockYard Academy repository as this will spam our PR tracker.

DockYard Academy Students Only:

Notify your teacher by including @BrooklinJazz in your PR description to get feedback. You (or your teacher) may merge your PR into your solutions branch after review.

If you are interested in joining the next academy cohort, sign up here to receive more news when it is available.

Up Next

Previous Next
Credo Games: Documentation and Static Analysis