Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lesson_07/homework/my_music_band/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@

Давайте соберём музыкальную группу и сыграем небольшой концерт :)

Группа (Band) состоит из трёх музыкантов:
Группа (Band) состоит из трёх музыкантов:

- вокалист (Vocalist),
- гитарист (Guitarist),
- и барабанщик (Drummer).

У каждого музыканта своя партия, которая представлена списком звуков:

```elixir
sounds = [:BOOM, :Ts, :Doom, :Ts]
```

Музыкант изучает эту партию:

```elixir
{:ok, drummer} = Drummer.init(sounds)
```

И затем играет бесконечно по кругу:

```elixir
{:BOOM, drummer} = Drummer.next(drummer)
{:Ts, drummer} = Drummer.next(drummer)
Expand All @@ -29,6 +33,7 @@ sounds = [:BOOM, :Ts, :Doom, :Ts]
```

У каждого музыканта свой набор звуков, которые он может исполнить. Смотри `MyMusicBand.Model.Sound`. Если дать музыканту партию с другими звуками, то он не сможет её играть:

```elixir
sounds = [:BOOM, :Ts, :Doom, :BoBoom, :Ts, :Ts, :Woooom, :Ts]
{:error, [{4, :BoBoom}, {7, :Woooom}]} = Drummer.init(sounds)
Expand All @@ -37,13 +42,15 @@ sounds = [:BOOM, :Ts, :Doom, :BoBoom, :Ts, :Ts, :Woooom, :Ts]
То есть, функция инициализации должна проверить все звуки, и вернуть список неправильных звуков с указанием их позиции в списке. (Позиции считаются от 1 а не от 0).

Когда каждый музыкант готов играть свою партию:

```elixir
{:ok, vocalist} = Vocalist.init([:'A-a-a', :'O-o-o', ...])
{:ok, guitarist} = Guitarist.init([:A, :D, ...])
{:ok, drummer} = Drummer.init([:BOOM, :Ts, ...])
```

их можно собрать вместе:

```elixir
band =
Band.init()
Expand All @@ -53,13 +60,14 @@ band =
```

и играть все партии вместе:

```elixir
{[:'A-a-a', :A, :BOOM], band} = Band.next(band)
{[:'O-o-o', :D, :Ts], band} = Band.next(band)
```

При этом нужно учесть, что партии могут быть разной длины.

Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании).
Вам нужно реализовать модули `Vocalist`, `Guitarist`, `Drummer` и `Band` так, чтобы они прошли тесты. Модуль `Sound` уже реализован, его менять не нужно (хотя можете добавить свои звуки при желании).

Если считаете нужным, можете добавить ещё какие-то модули.
28 changes: 24 additions & 4 deletions lesson_07/homework/my_music_band/lib/band.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
defmodule MyMusicBand.Band do
alias MyMusicBand.Player

def init(), do: nil
defstruct players: []

def add_player(arg1, arg2), do: nil
@type t() :: %__MODULE__{
players: list(Player.t())
}

def next(arg), do: nil
@spec init() :: t()
def init(), do: %__MODULE__{}

end
@spec add_player(t(), Player.t()) :: t()
def add_player(band, player) do
%{band | players: band.players ++ [player]}
end

@spec next(t()) :: {list(atom), t()}
def next(band) do
{sounds, updated_players} =
Enum.map(band.players, fn player ->
Player.next(player)
end)
|> Enum.unzip()

new_band = %{band | players: updated_players}
{sounds, new_band}
end
end
20 changes: 17 additions & 3 deletions lesson_07/homework/my_music_band/lib/drummer.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
defmodule MyMusicBand.Drummer do
alias MyMusicBand.Model.Sound
alias MyMusicBand.PlayerLogic

def init(arg), do: nil
@derive MyMusicBand.Player

def next(arg), do: nil
defstruct [:sounds, :current_stream]

end
@type t() :: %__MODULE__{
sounds: [Sound.drum_sound()],
current_stream: Enumerable.t()
}

@spec init([Sound.drum_sound()]) :: {:ok, t()} | {:error, [{integer(), :atom}]}
def init(sounds) do
PlayerLogic.init(%__MODULE__{}, sounds, &Sound.is_drum/1)
end

@spec next(t()) :: {atom(), t()}
def next(drummer), do: PlayerLogic.next(drummer)
end
20 changes: 17 additions & 3 deletions lesson_07/homework/my_music_band/lib/guitarist.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
defmodule MyMusicBand.Guitarist do
alias MyMusicBand.Model.Sound
alias MyMusicBand.PlayerLogic

def init(arg), do: nil
@derive MyMusicBand.Player

def next(arg), do: nil
defstruct [:sounds, :current_stream]

end
@type t() :: %__MODULE__{
sounds: [Sound.guitar_sound()],
current_stream: Enumerable.t()
}

@spec init([Sound.guitar_sound()]) :: {:ok, t()} | {:error, [{integer(), :atom}]}
def init(sounds) do
PlayerLogic.init(%__MODULE__{}, sounds, &Sound.is_guitar/1)
end

@spec next(t()) :: {atom(), t()}
def next(guitaris), do: PlayerLogic.next(guitaris)
end
25 changes: 17 additions & 8 deletions lesson_07/homework/my_music_band/lib/model/sound.ex
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
defmodule MyMusicBand.Model.Sound do
@moduledoc """
Identifies valid sounds for each type of musician.
"""

@type silence() :: :' '
@type silence() :: :" "
@silence :" "

@type note_a() :: :'A-a-a'
@type note_c() :: :'O-o-o'
@type note_e() :: :'E-e-e'
@type note_g() :: :'Wooo'
@type note_a() :: :"A-a-a"
@type note_c() :: :"O-o-o"
@type note_e() :: :"E-e-e"
@type note_g() :: :Wooo
@type vocal_sound() :: note_a() | note_c() | note_e() | note_g() | silence()
@vocal_sounds [:"A-a-a", :"O-o-o", :"E-e-e", :Wooo, @silence]
def is_vocal(sound), do: sound in @vocal_sounds

@type accord_a() :: :A
@type accord_d() :: :D
@type accord_e() :: :E
@type guitar_sound:: accord_a() | accord_d() | accord_e() | silence()
@type guitar_sound() :: accord_a() | accord_d() | accord_e() | silence()
@guitar_sounds [:A, :D, :E, @silence]
def is_guitar(sound), do: sound in @guitar_sounds

@type kick() :: :BOOM
@type snare() :: :Doom
@type hi_hat() :: :Ts
@type drum_sound :: kick() | snare() | hi_hat() | silence()
@type drum_sound() :: kick() | snare() | hi_hat() | silence()
@drum_sound [:BOOM, :Doom, :Ts, @silence]
def is_drum(sound), do: sound in @drum_sound

@type sound() :: vocal_sound() | guitar_sound() | drum_sound()

end
21 changes: 21 additions & 0 deletions lesson_07/homework/my_music_band/lib/player.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defprotocol MyMusicBand.Player do
@moduledoc """
Define the protocol for all musicians in the band.
"""
alias MyMusicBand.{Vocalist, Drummer, Guitarist}

@type player() :: Guitarist.t() | Drummer.t() | Vocalist.t()

@doc "Returns the next sound and the updated state"
@spec next(player()) :: {atom(), player()}
def next(player)
end

defimpl MyMusicBand.Player, for: Any do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Доброго вечера! Прошу прощения за мои 5 копеек!

Какой смысл объявлять протокол для Any? Protocol == полиморфизм. Его суть как раз-таки в том, чтобы написать различные имплементации для одной и той же функции в зависимости от типа входного параметра.

Пример из документации https://hexdocs.pm/elixir/1.12.3/Protocol.html

defprotocol Size do
  @doc "Calculates the size (and not the length!) of a data structure"
  def size(data)
end

defimpl Size, for: BitString do
  def size(binary), do: byte_size(binary)
end

defimpl Size, for: Map do
  def size(map), do: map_size(map)
end

defimpl Size, for: Tuple do
  def size(tuple), do: tuple_size(tuple)
end

Вы можете сделать импорт PlayerLogic и будет то же самое.
Вероятно, нужно посмотреть в сторону @behaviour.
Behaviour обычно и используется для абстрагирования логики, причём вызываемый модуль при помощи коллбеков @callback говорит вызывающему коду, как нужно работать с данным вызываемым модулем.

P.S. Ещё раз прошу прощения, что влез 🙏

Copy link
Author

@bylevskijSV bylevskijSV Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привет, никаких проблем) Можно и нужно высказываться)
Я понимаю про что Вы говорите, но в данном случае просто проба пера протокола и я тут согласен - он не нужен)
А Any и derive это просто лень =) Чтобы для каждого, опять, не писать одно и тоже, а вернее меньше одного и того же)))
Поэтому есть реализация через callback, которая мне больше нравится.

alias MyMusicBand.{Vocalist, Drummer, Guitarist, Player}

@spec next(Player.player()) :: {atom(), Player.player()}
def next(player) do
MyMusicBand.PlayerLogic.next(player)
end
end
41 changes: 41 additions & 0 deletions lesson_07/homework/my_music_band/lib/player_logic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule MyMusicBand.PlayerLogic do
@moduledoc """
Contains common logic for all modules that implement Player behavior.
"""

alias MyMusicBand.Player

@spec init(struct(), list(atom), (atom() -> boolean())) ::
{:ok, Player.t()} | {:error, list({pos_integer(), atom})}
def init(struct, sounds, validator_fun) do
errors =
sounds
|> Enum.with_index(1)
|> Enum.reduce([], fn {sound, index}, acc ->
if validator_fun.(sound), do: acc, else: [{index, sound} | acc]
end)
|> Enum.reverse()

case errors do
[] -> {:ok, %{struct | sounds: sounds, current_stream: Stream.cycle(sounds)}}
_ -> {:error, errors}
end
end

@spec next(Player.t()) :: {atom, Player.t()}
def next(%{current_stream: stream} = player) do
{sound, new_stream} = get_sound(stream)
new_player = %{player | current_stream: new_stream}
{sound, new_player}
end

defp get_sound(stream) do
case stream |> Stream.take(1) |> Enum.to_list() do
[sound] ->
{sound, stream |> Stream.drop(1)}

_ ->
{nil, stream}
end
end
end
20 changes: 17 additions & 3 deletions lesson_07/homework/my_music_band/lib/vocalist.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
defmodule MyMusicBand.Vocalist do
alias MyMusicBand.Model.Sound
alias MyMusicBand.PlayerLogic

def init(arg), do: nil
@derive MyMusicBand.Player

def next(arg), do: nil
defstruct [:sounds, :current_stream]

end
@type t() :: %__MODULE__{
sounds: [Sound.vocal_sound()],
current_stream: Enumerable.t()
}

@spec init([Sound.vocal_sound()]) :: {:ok, t()} | {:error, [{integer(), :atom}]}
def init(sounds) do
PlayerLogic.init(%__MODULE__{}, sounds, &Sound.is_vocal/1)
end

@spec next(t()) :: {atom(), t()}
def next(vocalist), do: PlayerLogic.next(vocalist)
end