-
-
Notifications
You must be signed in to change notification settings - Fork 194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Learning Mode] Discussion: building up to Pattern Matching #1103
Comments
The solution to Valentine's is feature-complete, so to speak. It matches on a custom ADT ( Here are a few variations of a translation of the F# exemplar solution. (The variation is in syntax alone.) Show code{-# LANGUAGE LambdaCase #-}
-- The above enables a well-established language extension
module ValentinesDay (rateActivity) where
data Approval = Yes | No | Maybe
data Cuisine = Korean | Turkish
data Genre = Crime | Horror | Romance | Thriller
data Activity = BoardGame | Chill | Movie Genre | Restaurant Cuisine | Walk Integer
-- How I would write it, but requires LambdaCase extension
rateActivity :: Activity -> Approval
rateActivity = \case
Restaurant Korean -> Yes
Restaurant Turkish -> Maybe
Movie Romance -> Yes
Walk kilometers
| kilometers < 3 -> Yes
| kilometers < 5 -> Maybe
_ -> No
-- Without LambdaCase
rateActivity' :: Activity -> Approval
rateActivity' activity =
case activity of
Restaurant Korean -> Yes
Restaurant Turkish -> Maybe
Movie Romance -> Yes
Walk kilometers
| kilometers < 3 -> Yes
| kilometers < 5 -> Maybe
_ -> No
-- Using function definition syntactic sugar
rateActivity'' :: Activity -> Approval
rateActivity'' (Restaurant Korean) = Yes
rateActivity'' (Restaurant Turkish) = Maybe
rateActivity'' (Movie Romance) = Yes
rateActivity'' (Walk kilometers)
| kilometers < 3 = Yes
| kilometers < 5 = Maybe
rateActivity'' _ = No
|
This is really nice. My gut feeling is that the middle solution is just right at this point in the concept tree. I do like the clean approach of the first one, but I think that we should probably wait to introduce language extensions. Similarly I think it's worth deferring the function definition syntactic sugar until later in the concept tree. |
I agree. The third one is not any more complicated (depending on their introduction to the language, a beginner might even find it simpler), but it is noisier.
While I agree on this, a warning: the language extensions are very diverse, and so are their appropriateness, need, and difficulty level. Intuitions from other languages about them do not necessarily transfer. To paraphrase Alexis King: the question is less «is introducing language extensions a good idea?» and more «is using this language extension a good idea?».
Many Haskell tutorials start out with the sweet version and introduce I think it's safe to introduce them together. Wouldn't know when to do it otherwise either: PM2 is already kinda heavy, and I do not feel like this syntactic sugar deserves its own concept node. |
Yes, that's my sense, exactly. The three actually feel really similar to me in terms of difficulty, but I think the middle example strikes a good balance.
Yepp, that makes sense. Cool. So, if @ErikSchierboom also agrees that the middle example is the way to go, I think we'd be good to kick off work on the Valentine's Day exercise itself. @MatthijsBlom Have you given any thought yet to how you'd introduce Algebraic Data Types? |
It looks like a straightforward translation of F#'s
Working on it right now. (On that whole graph section actually.) |
It occurs to me that |
So your saying you wouldn't use pattern matching when working with them? |
I agree! |
Which of these concepts will be prerequisites and which of these concepts will be newly introduced concepts? |
@MatthijsBlom Do you have some snippets of code as starting points for discussion for some of the earlier concepts in the graph here (First |
I would regularly, but a beginner need not immediately. The utility functions in their respective modules are sufficient for everything, if in a slightly different way. Seems fine practice in composing functions. ...But that's not really what that was about: I tried to say that the graph might look like this instead:
Guards are part of Simple Pattern Matching; the rest will be introduced in Pattern Matching Proper.
Not yet, but these are the kinds of signatures I'm thinking of:
ADTs: the data type declarations in Valentine's Day contain most of this. The only thing that is really missing is polymorphism: Tuples are plumbing, so I do not expect appealing code. However, the likes of Looks like lists will get their own node early on. I haven't checked this, but I imagine that lots of material from other tracks on lists can be reused. (Expecially Purescript, Elm.) |
As a quite rusty Haskell programmer, code samples would be great, yes. I must admit that things don't readily "click" in my mind currently, but I'm sure I'll get there. I think the code samples will be invaluable. |
This is taking way longer than expected, and @pwadsworth is being productive in my absence, so I tried to write down my thoughts here below. Pattern Matching & Data Type DeclarationsHere is an attempt at a somewhat thorough outline of things related to pattern matching and data types in Haskell. Explanations are omitted, as are obviously 'advanced' concepts like GADTs. Some such concepts however are included, simply because it was easier for me to include than to omit them. Pattern Matching syntax
f x =
case x of
pattern -> expression Function definition syntactic sugar (compiles to f pattern = expression Patterns(Please disregard GitHub's broken highlighting of x = case someExpression of
-- Wildcard pattern
_ -> expression
-- Binding pattern
name -> expression
-- Regular constructor pattern
Constructor pat ter ns -> expression
-- (Named) "as" pattern
name@pattern -> expression
-- Special syntax reused as pattern syntax
5 -> expression -- integers
-3.7 -> expression -- fractional numbers
"Hello!" -> expression -- strings
[x, y, z] -> expression -- lists
(x, y) -> expression -- tuples
-- Bang pattern
!pattern -> expression
-- Irrefutable pattern
~pattern -> expression
-- Guards
pattern | booleanExpression -> expression -- boolean guard
| pattern <- expression -> expression -- pattern guard
| let name = expression -> expression -- local binding
| guard, guard, guard -> expression -- sequence of guards Data type declarations-- "data declaration"
data TypeConstructor typeParameters =
DataConstructor fi El Ds
| DataConstructor' Fi eld S
-- "newtype declaration"
newtype TypeConstructor typeParameters =
DataConstructor Field
-- "type declaration"
type TypeConstructor typeParameters = someType
-- deriving clause
data T a b c = C a b c deriving (classes)
newtype N a b c = N a b c deriving (classes) Of note:
RecordsTBA
Note: while they look as similar to each other as some other constructs shared between the languages, the records of Haskell and Elm are not to be confused: they are different beasts. Examples of data types and how to work with them
Each of these except
Note: sadly tuples and lists are not great examples, because their syntax is special on many levels (syntactic sugar + weird names of both type and data constructors). Each of these types can be worked with, even without (explicit) pattern matching, using the utility functions available from dedicated standard library modules. What to teach whenFor reference the above list condensed(Unordered)
I propose we do not teach
I have marked these with 🛑 in the condensed list above. In addition, I wouldn't be too sad if we skip
I have marked these 😢 in the condensed list above. Now, assuming the concept graph proposed earlier, … Which graph? This one.
…I was thinking
I realize these ideas conflict with ongoing work. What do you think about the above, @pwadsworth? If it helps, I can produce some Footnotes
|
@MatthijsBlom Wow, that is a lot of information! As mentioned, we (the Exercism team) would like to have the next step be some sample code for the various concepts. @pwadsworth would you be interested in writing some sample code for the various concepts? edit: lets focus on 1 concept at a time, preferrably the one with the least amount of prerequisites |
I'm guessing you mean code samples for the Example Nodes, as a Simple Pattern Matching has already been merged.
That would be Anyway, I failed at this. What follows is the same treatment for all three of these types. If this is bothersome, I can edit some out. I had previously published a few solutions that 'use'
(PureScript and Haskell are closely related dialects; the few differences do not matter here.) Some observations on the use of
All this is normal in Haskell (and, I suspect, in PureScript as well). (Are there other possible 'uses', besides construction and inspection?) We tend to avoid creating functions that take these types as arguments. Most cases by far are covered by plumbing functions like All three of I previously mentioned that a typical type signature with Binary Search in Haskellfind :: Ord a => Array Int a -> a -> Maybe Int
find arr x = uncurry go (bounds arr)
where
go lo hi
| lo > hi = Nothing -- 👈
| otherwise =
let m = (lo + hi) `div` 2
in case compare (arr ! m) x of
LT -> go (m + 1) hi
EQ -> Just m -- 👈
GT -> go lo (m - 1)
-- Two constructions, no intermediate values,
-- and again no inspection. As expected, the actual implementation hardly adds to the While the above examples may show typical use of Role Playing Game (
|
@MatthijsBlom Sorry that I've not yet replied. I'm incredibly swamped right now :( |
Sorry for the delayed reply. I have been super busy and haven't had any time to dedicate here. I expect to have more time available in late November and December. If we settle on the way forward before then I'll be able to write several exercises and related docs. @MatthijsBlom: IRT conflict with what we have already done, I do not have any issue adjusting to improve the learning path, but what you proposed has many concepts in each node. I thought the idea was to keep the number of concepts to one or two per node to make each a small incremental step for students. I can see benefits in clarity of exposition, and nodes being self-contained though, so I have no problems with expanding the nodes already written if the Exercism team wants to take that route. I do have a couple minor shown below in bold on where some concepts are better introduced. @ErikSchierboom: Assuming the above, here is what I propose to do for each.
|
This is spot on. We don't want many concepts explained in a single exercise. If we build an exercise and it turns out that there are many concepts it introduces, we should then introduce more exercises (which will serve as prerequisites) to introduce some of those concepts. So what would be a logical next step? We have |
It cannot be that simple, as concepts are often neither atomic nor uniformly granular. It does not make as much sense as I would like to speak of numbers of concepts; one should instead think of concept weights. Example illustrating this point; feel free to skip if you already believe me.Pattern matching has a conceptual core: to check, one by one, a number of 'patterns' for 'matching' a specific value, and then acting on which 'pattern' turns out to 'match'. Aside from this core, pattern matching comes in a number of forms (see my list of kinds of patterns). To put pattern matching into practice, one must use (at least) one of these forms! Introducing the conceptual core by itself is impossible. Similarly, introducing any one form without the conceptual core is impossible. On the other hand, introducing all types of patterns at once would be overwhelming and confusing. Guards are (optional) pattern building blocks, but they do not exist by themselves so they cannot be introduced before patterns. A significant complication, it would be unwise to (wholely) introduce them alongside the conceptual core. In conclusion, the Pattern Matching concept cannot be introduced as a whole (and it isn't, presently), and neither can it be introduced entirely piecewise. A choice must be made how to lump together the many partial concepts. By my estimation (based on observed student behavior), the core pattern matching concept is heavy, pattern forms are individually light but collectively heavy, and (boolean) guards are moderately heavy but undesirably attractive. Of course, thinking of weights has problems, chief among them that it is subjective. Still, I do not see a better way of thinking about it.
Neither of these cover single concepts (even though their names suggest they do):
As illustrated by my table, if it were up to me the syntactic sugar would be postponed, the binding patterns would too (as absent both sugar and Which is to say: I don't think we have these yet. (Unless it is decided that we actually do and should move on.)
A bit off-topic, but: lists (excluding pattern matching on them) could be introduced independently, and Regarding the pattern matching concept-cluster: assuming the current repo state, I don't know. Going through @pwadsworth's table:
See the large code block listing all the kinds of patterns above (page-search
Are you sure? All of these are presently treated by
Do you mean for "ADTs" to come after "PM Proper"? The exemplar Valentine's Day solution uses guards and binding patterns. (I'm assuming you mean to keep Valentine's Day's association with
This seemed to me like a concept hard to explain without lots of examples. But since we were going to introduce
Are you sure? I mean declarations of the form
I agree, but we cannot avoid
@pwadsworth You forgot to include these.
Guilty as charged. However, I have tried to reasonably distribute weight among the nodes:
I thought I'd mentioned it somewhere, but I cannot find it anymore: I have thought about moving guards into their own node. It might keep students from developing bad habits a bit, and make the rest of the pattern matching nodes lighter. It would also allow to go more in-depth on guards. |
@MatthijsBlom Thank you for this in-depth exploration into your thinking on the subject. Let's start with the exercise for Pattern Matching Proper, making the assumption that Algebraic Data Types, Maybe, and Either have all been explained before. Then let's work backwards from there. If we find that it becomes necessary, we can always create additional prerequisite exercises later. If I recall correctly, we settled on using Valentines Day for the story for Pattern Matching Proper. @MatthijsBlom @pwadsworth Does this sound like it will work? |
To me it does. As I noted before, Valentine's Day is 'feature complete': it has (the most common) patterns, boolean guards, and a |
hey folks, sorry to jump in here but I'm interested in helping out with getting Haskell learning mode + concept exercises built out. It seems like we still need some exercises for
|
Yes, we very much do. I have been working on the concept introductions with some success, but have had trouble finding compelling & suitable exercises to go with them. #1106 contains a draft for the introduction of I have more draft work (
No. It is customary to use dedicated Learning Exercises for the syllabi, and these are all Practice Exercises. Learning Exercises are typically extremely simple and hyperfocused on the concept in question. We could adopt existing stories, or craft our own. |
Got it, thanks for explaining. I will try to take a look at the existing stories to see if there are any we could use or take inspiration from for crafting our own if we have to. |
OK after looking through the list I couldn't see anything obvious that would be a good candidate, so perhaps it's better to come up with something ourselves. For constructing Maybe values, there are a couple of simple and canonical ideas:
For using stuff from
|
I like the Lists haven't been introduced yet, so I am a bit hesitant to require using I do not think it is healthy to encourage using On my wish list are |
@MatthijsBlom suggested the following concept graph, which is a good starting point for building up to a basic understanding of proper pattern matching in Haskell.
I'd like to start the discussion by looking at some sample code that demonstrates the end point, Pattern Matching Proper. It doesn't have to relate to any exercise or story, rather it's a way to make sure that everyone involved understands where we're trying to get to.
@MatthijsBlom I know you've been thinking a lot about this over the past couple of weeks. Would you be willing to write a few (maybe 10 or so?) lines of code to illustrate Pattern Matching?
The text was updated successfully, but these errors were encountered: