exercism / v3

The work-in-progress project for developing v3 tracks
https://v3.exercism.io
Other
170 stars 162 forks source link

[Elixir] Implement new Concept Exercise: access-behavior #1479

Closed neenjaw closed 4 years ago

neenjaw commented 4 years ago

This issue describes how to implement the access-behavior concept exercise for the Elixir track.

🔗 How to help

Getting started

Please please please read the docs before starting. Posting PRs without reading these docs will be a lot more frustrating for you during the review cycle, and exhaust Exercism's maintainers' time. So, before diving into the implementation, please read up on the following documents:

Please also watch the following video:

Goal

The goal of this exercise is to teach how to use the access behavior to retrieve data from nested datastructures which implement the behavior. Out of the box, maps and keyword lists implement the access behavior.

Learning objectives

Out of scope

Concepts

Prerequisites

Narrative for exercise

Following the story outlined here, students should implement the task.

The task should be broken into sub-components which then culminate in the lesson.

Resources to refer to

Hints

After

Representer

Analyzer

Actionable items 🔗

looking for help in a few ways, and I am here to help you through getting this exercise off the ground:

For a first phase review we need:

Thanks for considering!

Need Help?

If you have any questions while implementing the exercise, please post the questions as comments in this issue.

angelikatyborska commented 4 years ago

Some questions from me: 1) How do the functions get_in and update_in relate to this exercise? Should be covered by it or not? And Access.key?

2) Lists need to be avoided in this exercise because they do not support the access behavior. If we compromised and said "use Enum.at", we would be giving a bad example as lists are not meant for random access. Correct?

3) I have no idea about basketball. How can "lots of data" about a basketball team look like? There's this example in JS but I'm not sure how to translate it well into Elixir to avoid lists:

const team = {
  players: [
    {
      name: "Becky",
      position: "Point guard"
    },
    {
      name: "Maya",
      position: "Center"
    }
  ]
};
neenjaw commented 4 years ago

Good questions:

  1. I'm not sure get_in or update_in do directly unless we want to add that as a subsequent task. Which might be a good thing since they are useful and common. Access.key is the static access operator reference in the out-of-scope, I think that might work better once structs are introduced.

  2. We could avoid it if that is what works best. This problem is taken from the JS reseach problem, which can sometimes be a bit tricky since they are open ended without guidance, but here we have the opportunity to break problems down to guide students through tasks. If we didn't want to avoid it, we could place an arbitrary condition on the keys that any numeric key is actually nth element of a list. This might be counter productive since numbers can be keys in a map structure.

  3. For data there could be:

    basketball_team = %{
    players: [
    %{
      name: "Becky",
      position: "Point guard",
      player_number: 98,
      average_points_per_game: 6.8,
      games_played: 12
    },
    %{
      name: "Maya",
      position: "Center",
      player_number: 34,
      average_points_per_game: 6.8,
      games_played: 4
    }
    ],
    coach: "Chris Marlin",
    sponsors: "Dashbit"
    }

This is a pretty good list of them here: https://en.wikipedia.org/wiki/Basketball_statistics

neenjaw commented 4 years ago

I think I will try to tackle this one, then going to spend some time making up some more issues.

neenjaw commented 4 years ago

It's interesting, part 1 of that basket ball problem is very easy (traversing the list map keys)

  def extract(data, path) do
    paths = String.split(path, ".", trim: true)
    do_extract(data, paths)
  end

  def do_extract(nil, _), do: nil
  def do_extract(data, []), do: data
  def do_extract(data, [path|next]) do
    do_extract(data[path], next)
  end

But part two is kind of a nastier one for students I think because lets suppose:

# Team data
data = %{ "coaches" => [%{ "name" => "Jane" }], "name" => "Hoop Masters" }
# Path to extract
search_string = "coaches[0].name"

getting coach[0] is easy as an extension to part 1. But then how to structure the exercise to separate coach from [0] and then how to get 0 from [0].

You could:

  1. use a regex and two captures
  2. convert the string to a list of graphemes, then recurse on the list looking for [x] then returning all of the digits at x, but then you also need to split the string to use the coaches key
  3. recurse to find the position of [ then split at that point
  4. and so on...

What I am trying to get across is that I think it is much harder to do that I thought and my thoughts about the actual prerequisites is much larger than i describe above:

So some questions I am posing:

How much can I assume a student knows about regex so that we don't have to write a novel about regex parsing? How much can I expect a student to do? (e.g. write a string parser with accumulators, etc.) I think the v2 answer would be "write a string parser" then you'd hash it out with a mentor, but I don't think that's the v3 approach here.

So why?

I think for someone coming from javascript, they are going to want a way to write "string".indexOf("s") and get a discrete answer to then "string".substring(0, i), which elixir should have a robust analogous way to translate that idea from one language to another.

I am just not sure which is the right way to do this for maximum benefit.

So I'm going to put out the :bat:-signal! @Cohen-Carlisle @chriseyre2000 @NobbZ @angelikatyborska @ErikSchierboom

ErikSchierboom commented 4 years ago

How much can I assume a student knows about regex so that we don't have to write a novel about regex parsing?

I'd say: don't assume that the student knows anything about regular expressions actually, as I know many people actually don't know much about regular expressions.

What I am trying to get across is that I think it is much harder to do that I thought and my thoughts about the actual prerequisites is much larger than i describe above:

Perhaps this would suggest changing this part of the exercise a bit to make it better suited for Elixir?

I think for someone coming from javascript, they are going to want a way to write "string".indexOf("s") and get a discrete answer to then "string".substring(0, i), which elixir should have a robust analogous way to translate that idea from one language to another.

This is hard to do in Elixir?

I have no idea about basketball. How can "lots of data" about a basketball team look like?

I can help with this. Ping me whenever you want some basketball information or suggestions.

neenjaw commented 4 years ago

I'd say: don't assume that the student knows anything about regular expressions actually, as I know many people actually don't know much about regular expressions.

:+1: Ok, so I guess I'll plan for a regex exercise (or two) to precede this one.

Perhaps this would suggest changing this part of the exercise a bit to make it better suited for Elixir?

Agreed, I am thinking it will come to that

This is hard to do in Elixir?

I wouldn't say hard, but (at least I) don't know a direct translation of this idea from C-family languages without going to an erlang function (:binary.match). There is often more of a focus to look at solving the intended problem (edit) in a more declarative way, or just by using a regex.

An interesting discussion here on Stack Overflow

But I think sleeping on this problem led me to this solution:

s = "coaches[0]"
[key, trailing] = String.split(s, "[", parts: 2)
index = String.trim_trailing(trailing, "]")

I can help with this. Ping me whenever you want some basketball information or suggestions.

:+1:

angelikatyborska commented 4 years ago

What if we completely avoid lists in this exercise? It's supposed to teach about access behavior, but lists don't support it. Lists could be replaced with maps like this:

["a", "b", "c"]

# becomes

%{
  "1" => "a",
  "2" => "b",
  "3" => "c"
}

And then also reformat keys "coaches[0].name" -> "coaches.0.name" to simplify parsing.

The philosophy of concept exercises is that everything in the exercise that is not related to the new concepts should be as simple as possible, right?

BTW., I am a bit worried about keyword lists. If we decide to include them using this story, would that mean telling students to take a string "key1.key2.key3", split it, and then transform it to atoms? Not the best lesson. Maybe it would be easier to come up with a new story, more suited for Elixir?

neenjaw commented 4 years ago

Yes, I think that is a good point, dynamically creating atoms is an anti-pattern to avoid.

I think the best thing to do is change the narrative to avoid this.

neenjaw commented 4 years ago

I withdraw the 🦇 -signal! Thanks for the help!

neenjaw commented 4 years ago
  @team_data = %{
    "coach" => %{
      "first_name" => "Jane",
      "last_name" => "Brown"
    },
    "team_name" => "Hoop Masters",
    "players" => %{
      "99" => %{
        "first_name" => "Amalee",
        "last_name" => "Tynemouth",
        "email" => "atynemouth0@yellowpages.com"
      },
      "98" => %{
        "first_name" => "Tiffie",
        "last_name" => "Derle",
        "email" => "tderle1@vimeo.com"
      },
      "23" => %{
        "first_name" => "Garfield",
        "last_name" => "Beeho",
        "email" => nil
      },
      "36" => %{
        "first_name" => "Clio",
        "last_name" => "Serfati",
        "email" => "cserfati3@comsenz.com"
      },
      "42" => %{
        "first_name" => "Conchita",
        "last_name" => "Elham",
        "email" => "celham4@wikia.com"
      },
      "61" => %{
        "first_name" => "Noel",
        "last_name" => "Fawlkes",
        "email" => "nfawlkes5@yahoo.co.jp"
      },
      "53" => %{
        "first_name" => "Lucine",
        "last_name" => "Russel",
        "email" => "lrussel6@fotki.com"
      },
      "12" => %{
        "first_name" => "Andy",
        "last_name" => "Napoli",
        "email" => "anapoli7@goodreads.com"
      },
      "9" => %{
        "first_name" => "Ivar",
        "last_name" => "Longson",
        "email" => "ilongson8@timesonline.co.uk"
      },
      "32" => %{
        "first_name" => "Deni",
        "last_name" => "Lidster",
        "email" => nil
      }
    }
  }

I think I'll adapt the story to work with map string keys, and also touch on get_in and put_in