elm icon indicating copy to clipboard operation
elm copied to clipboard

Concept: generics

Open mpizenberg opened this issue 4 years ago • 1 comments

Design

Goal

The goal of this exercise is to teach students how to consume and make generic code.

Learning objectives

  • Understand what a type variable is.
  • Know how to define generic types.
  • Know how to define generic functions.
  • Know how to use phantom types.

Out of scope

  • Covariance and contravariance.
  • Higher order functions.

Concepts

  • generics: Learn how to create and use Elm code that is generic.

Prerequisites

  • lists: Learn how to use lists in an Elm program.
  • records: Learn how to use records in an Elm program.
  • maybe: Learn how to use optional values with the Maybe type.

mpizenberg avatar Sep 19 '21 16:09 mpizenberg

Being worked on in #391

mpizenberg avatar Sep 19 '21 16:09 mpizenberg

Hi @mpizenberg @jiegillet, I've started thinking about this, and have some thoughts

Phantom types (from the learning objectives) are pretty advanced and rarely used (I've never used one), I think opaque types would be more appropriate, as they are much more common and a bit easier to understand.

I am thinking about using a TreasureChest for the exercise

type TreasureChest a = TreasureChest String a

The string would be a password to unlock the chest, and the type definition includes a generic.

getTreasure:  String -> TreasureChest a -> Maybe a

There would be a getTreasure function that returns the treasure if the password matches

Then we could make TreasureChest opaque, so that you can't retrieve the treasure without supplying the password.

Thoughts?

ceddlyburge avatar Nov 04 '22 11:11 ceddlyburge

I actually worked on this concept for a bit a long time ago, and I can't seem to seems all of my changes, the one (shameful) artifact I have is this file that I committed by mistake in a different PR. We should get rid of it. I was playing with the idea or "forcing" the students to implement a function purely on its type (kind of like there is only one meaningful implementation of always : a -> b -> a) and in the second part I was trying to include phantom types for currencies.

It was very hard to find something satisfying, I never finished. Maybe I could if we removed phantom types. So I'm OK with dropping phantom types, we could still make another separate concept later.

I like your idea, but I think opaque types can be its own concept, I'm not sure it fits with generics. Also for opaque types, we really need to drive home why this is an important design decision, so maybe we should find some counter example that illustrates the problem with public types.

jiegillet avatar Nov 04 '22 13:11 jiegillet

Hi all, if you think phantom types are a bit too much for the generics concept, it may be better to leave it for another later concept. However, they are extremely useful, and if you’ve never had to use them, I’d suggest watching Jeroen Engels presentation "The phantom builder pattern".

Regarding opaque type, I agree they are useful, but they have no relation to generics, and are more related to managing modules, code organisation, and public/private access.

mpizenberg avatar Nov 04 '22 14:11 mpizenberg

That presentation from Jeroen is great, but I've not had a situation where I wanted to use them yet, and I have used opaque types a few times. I think this is common among a lot of elm developers / elm code.

Anyway, how about we separate these out in to 3 concepts.

  • Generic types can use the basic treasure chest
  • Opaque types can make the treasure chest a lot better, and drive home why its an important decision, and contrast it to the basic treasure chest
  • Phantom types can maybe use the MoneyExchange that Jie has started (to be decided later).

ceddlyburge avatar Nov 04 '22 15:11 ceddlyburge

Hi @jiegillet , are y ou happy with the above proposal? Matthieu has given it the thumbs up, so I think he's happy ...

ceddlyburge avatar Nov 11 '22 07:11 ceddlyburge

To be more precise than a lazy "thumb up" ^^, I agree with separating in 3. As for how the split is done, I’m not super familiar with the exercises since I haven’t looked at them for a long time (or at all), so I trust you.

mpizenberg avatar Nov 11 '22 11:11 mpizenberg

I felt I had to reply to that with a "lazy thumbs up" :)

ceddlyburge avatar Nov 11 '22 11:11 ceddlyburge

I'm also OK with the idea of splitting into 3, but I confess that the treasure chest idea doesn't excite me very much. It's too close to a restricted version of Dict. If we are talking about generic types, I prefer more abstract things, in the direction of Currency a b that I had in that wrongly committed file, and implementing very generic functions like |> always identity, either andThen, things like that, or realizing that you can never make a function Maybe a -> a in Elm...

And for opaque types, I was thinking we should have an example that really showcases the need for the technique. Something like (untested code coming)

type alias SizedList a = { size : Int, list : List a}

add : a -> SizedList a -> SizedList a
add a {size, list} = SizedList (size +1) (a :: list)

head : SizedList a -> Maybe a
head {size, list} = if size == 0 then Nothing else List.head list

If SizedList a remains an alias, it's very easy to break the "sized" contract and get nonsense out of it head (SizedList 0 [1, 2, 3]) == Nothing. We could have the student "hack" a provided SizedList a to prove the point and them have them copy the API in an opaque way in a different file.

All that being said, I certainly don't want to write all of these concepts by myself, so whoever has the motivation to write one should follow their heart and do what they believe is worth teaching 😸

jiegillet avatar Nov 11 '22 12:11 jiegillet

Hi @jiegillet , I see what you mean, the concept exercises I am proposing are very simple.

I think what you are talking about (andThen, SizedList and suchlike) is important, but I think I would say it is a different concept (functors / applicative functors / monads is what I would call it).

A beginner Elm developer is likely to start using generics pretty early in their learning journey I think (quite a few of the practice exercises have the odd generic type, but usually as a side issue to the main problem), so I'm in favour of a simple exercise to teach it.

Writing your own functors / applicative functors / monads is usually a much later step in the learning / development journey. Nearly all of the time you can just use the ones that already exist.

So essentially I would like to keep a simple generic exercise, and I think TreasureChest will work fine (although it is very easy).

Opaque types are a more advanced topic, but I would say still not as advanced as functors and monads, so I would be a bit reluctant to make the simpler / more common thing dependent on the more complex / more rare thing. Your example of a SizedList is a good one though, so I'm a bit torn.

Maybe we could teach generics and opaque types initially with the two treasure chest exercises, and then teach opaque types again in the SizedList / functor exercise?

ceddlyburge avatar Nov 11 '22 14:11 ceddlyburge

For the record, I wasn't trying to introduce functors or monads at all, the other examples I mentioned (always, either) are more appropriate for where I was thinking. Anyway, I hear you, so let's create issues for all 3, you can get started on this one with the treasure chest, and we can take it from there.

Unrelated, but is there a reason we don't have a String concept? Seems like we should, it doesn't need to be complex.

jiegillet avatar Nov 11 '22 14:11 jiegillet

Ok, I'll do the TreasureChest one and we can see how we like it (and potentially get feedback from students), and maybe move to something more compelling later.

I think String might be covered a tiny bit in the basics-1 or basics-2 exercises? If not then its an oversight. Even if it is, it is probably worth it having its own concept to go in to a bit more of the detail. I can add an issue for that while I'm adding the type ones ...

ceddlyburge avatar Nov 11 '22 15:11 ceddlyburge