Concept: generics
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.
Being worked on in #391
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?
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.
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.
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).
Hi @jiegillet , are y ou happy with the above proposal? Matthieu has given it the thumbs up, so I think he's happy ...
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.
I felt I had to reply to that with a "lazy thumbs up" :)
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 😸
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?
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.
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 ...