Beta
Makeshift pattern matching
Loading description...
Algorithms
Fundamentals
Functional Programming
Recursion
View
This comment has been reported as {{ abuseKindText }}.
Show
This comment has been hidden. You can view it now .
This comment can not be viewed.
- |
- Reply
- Edit
- View Solution
- Expand 1 Reply Expand {{ comments?.length }} replies
- Collapse
- Spoiler
- Remove
- Remove comment & replies
- Report
{{ fetchSolutionsError }}
-
-
Your rendered github-flavored markdown will appear here.
-
Label this discussion...
-
No Label
Keep the comment unlabeled if none of the below applies.
-
Issue
Use the issue label when reporting problems with the kata.
Be sure to explain the problem clearly and include the steps to reproduce. -
Suggestion
Use the suggestion label if you have feedback on how this kata can be improved.
-
Question
Use the question label if you have questions and/or need help solving the kata.
Don't forget to mention the language you're using, and mark as having spoiler if you include your solution.
-
No Label
- Cancel
Commenting is not allowed on this discussion
You cannot view this solution
There is no solution to show
Please sign in or sign up to leave a comment.
It seems some of the tests have patterns that are simple objects, with set "matches" methods. Presumably we're meant to just call whatever the object's "matches" method is--regardless of where it has come from. I think that's how I'm doing it, but it wasn't clear to be from the description that how it must work.
It's explicitly saying you must set such methods on the prototypes. But that's not even necessary if you're just calling the method on the value you're given. So again, just a bit muddled with the potential of overlooking or misreading it.
This is covered by the following part of the description:
To expand:
patternMatch
is supposed to use[matchesSymbol]
on whatever it's given, and with its logic written that way you then need to define[matchesSymbol]
properties on built-in prototypes for it to behave correctly. Technically you could just defineObject.prototype[matchesSymbol]
, but it wouldn't be a very good way to do it.I was given this by the randomised tests:
But I think it's incorrect. The pattern is expecting an array of 3 items:
The input is an array containing 3 items:
...Right?
Because of these random tests, I don't think I'll be able to fully get them all to pass. There's usually only a few, but the number varies from run to run, so I'd only be getting lucky if I did pass it all. And when I look into the cases that don't pass, I don't think they're quite right by the looks of it.
So... it's a shame--I've sunk a few hours into this challenge, and it was pretty interesting. But I guess this is where I stop. ;/
The test you mentioned does look incorrect to me, but I can't reproduce the issue or figure out how it might have happened.
I've re-validated my own solution a few times with no issues, so if you are getting a lot of randomised test failures it is probably an issue with your solution.
This comment has been hidden.
This comment has been hidden.
I don't know if you test for objects with { valueOf } or { toString } that make them look like other objects, but that could be a good test.
Or even String objects with a set .toString method which messes things up, for example? Not sure how you want such things handled though.
toString
isn't considered and isn't supposed to have an effect on anything, butvalueOf
matters and is hinted at by the description at one point.This comment has been hidden.
There are no sample tests for
null
orundefined
that I got. Not strictly necessary--but to have such full coverage in the sample tests, but not quite all the basic ones seems like an oversight.This should be covered by the primitive tests.
"Properties do not need to be defined on the input for a match to succeed." Does that mean it has to be set on the input, but the value can be
undefined
? Can you havepattern={a:String}, input={}
and that's fine? What aboutpattern={a:String}, input={a:undefined}
? Not clear how this works, to me.What this is saying is that there is no special handling for a non-existent property and it should be treated the same as if it existed but had an undefined value. Sometimes things behave differently based on that, i.e. in some cases, if we have the following code:
Some things will treat
objA
andobjB
differently, even thoughobjA.prop
andobjB.prop
are bothundefined
. We don't want any special handling here, so the patterns{ prop: undefined }
and{ prop: () => true }
should match bothobjA
andobjB
.The first Function sample test is telling me "(Number)[matchesSymbol] should be a function." It is a function. So... 🤷
Also, I don't know why it's checking here.
Then later I set up a "(Function)[matchesSymbol]" and that resolved itself. Some weird messages being given here. Perhaps a bug or mis-type in the tests?
Possibly the confusion here is because that test is checking for
[matchesSymbol]
onNumber
(a function) and notNumber.prototype
.How to compare Sets is not specified. I passed the sample tests just by checking
.\_\_name
. That can't be all, surely?As with Symbols, Sets are supposed to be compared with
===
. The__name
thing is used by the tests for display purposes, it isn't supposed to be accessed by the solution.How to compare a Symbol to a Symbol isn't specified.
Technically every comparison is specified - anything that isn't explicitly covered in the description is supposed to use the fallback logic at the bottom of the Details section (i.e. basically
===
).Just reading the description, there's a number of ambiguities or confusing parts that I'd suggest need adjusting:
patternMatch()
function needs to do, and let them do it however they wish. That would be exactly the same amount of challenge, they can choose if they want to split code across the prototypes or not, etc.function(n) { return n > 10; }
would make sense to match values over 10; that would be a common way of using such apatternMatch()
function. But no number uses that function's prototype, so it will never pass the first condition. I recommen making it explicit if one or both are required for a function to match on a value.===
or something. As you say later: "A pattern not covered by the above rules matches if it is considered equal to the input. The equality check should be a strict equality check." But maybe you literally mean, run the pattern matching algorithm between each item in the two arrays? To resolve this, maybe define "match" at the top. Like, "Valuea
matches valueb
, ifa.matches(b)
is truthy." Perhaps the issue is, you started by talking about patterns matching values. But further down you're talking about values matching values, and also sometimes "pattern matching" is just "===". So it all just gets a touch messy while reading and trying to understand. Something like this could help clear things up for people on first-read._()
function instead of just returningtrue
. I guess maybe some sort of "functional" thing, or monads, or whatever... but if the user wants to write the code that way, they'll know how to writeconst _ = () => true;
. Not a big deal, just felt superfluous to me.All that said, I'm looking forward to trying out this kata now! Just wanted to get those thoughts out there while it was fresh.
Regarding the whole "matches" clarity issue, same goes for the section on objects, as well as arrays.
The idea is that a symbol is the better approach because it guarantees there will be no name conflicts (i.e. things don't break if we have a user-defined type with a
matches
method that does something different, ormatches
gets added to theRegExp
object and doesn't do what we want it to). Understanding of more advanced JavaScript concepts is intended to be part of the kata's challenge.It enables it because it allows you to implement it. It's providing a hook for custom pattern matching behavior - if one doesn't exist then you can't define your own pattern matching logic for your own types.
I mean, the class implements [matchesSymbol], so it will work. :)
IIRC I originally wrote it that way, but then I realized basically everyone will build something that isn't extensible, which wouldn't be ideal. I redefined everything in terms of
[matchesSymbol]
so that a passing solution has to be extensible.Technically the
[matchesSymbol]
handling could be required only for custom stuff, but I think it's better if everything is consistent.The hope is that it can be figured out by looking at the bullet points in the Details section. If you treat the Details section as a technical spec/strange form of pseudocode, you should find that it actually spells out most of a working solution for you.
It's at least one. I'll update the description to clarify. EDIT: Done.
See above point about the Details section being like a technical spec - you should find that "match" always has the same meaning throughout, i.e. in this case it's expecting you to recursively apply the full matching logic.
The idea is that there is no distinction - a pattern is always a value.
Back to the point about Details being like a spec - "X matches Y" terms always have the same meaning, i.e. it's "<pattern> matches <input>".
It makes things more concise and brings the "syntax" in line with what you find for pattern matching in a lot of other languages. It's a pre-defined variable so your solution shouldn't need to do anything about it.
Stringify function is incorrect for
Set
: it stringifiesSet
instances asSet([])
instead ofnew Set([])
, so the stringified value will always throw an error. Other primitive wrappers correctly containsnew
.This should be fixed now.
Needs additional fixed test cases for matching falsy values with
{}
:Also needs more tests for
Set
s:I've added
NaN
/false
/0
/''
tests under "Object tests". Thenull
/undefined
cases were already there.I've added those test cases.
Why go to all this trouble when you can just write
?
Because it's cool!
In fairness, I'd never use this in a real project, but I thought it would make a neat kata.
I agree the idea is neat, but the implementation seems to turn into just handling special case after special case, and sometimes special case within special case.
I haven't solved yet, but the idea seems better than the implementation and execution. JS has very inadequate support to do what is asked, and particularly the encoding of constructor instances goes actively against how the objects used for patterns work.
It seems any solutions will necessarily be kludges, instead of showcases of elegance. ( Please prove me wrong! )
Hopefully you will think my solution (once you can see it) is reasonably clean.
Is this a test on the pattern or on the input?
Ie, can I skip ( almost ) all pattern matching after a pattern of
null
, or on an input ofnull
?It's the pattern, so you can skip almost all matching if the pattern is null. Other patterns (e.g.
() => true
) can match null.The wording for this is now "
null
matches if the input isnull
".Is
pattern[matchesSymbol]
actually an invisible property of thepattern
argument that will be passed to the solution function by testing? This is not clear -pattern
is not really defined as an ( odd-indexed ) argument to the solution function ( some strategically placed backticks would help ), no examples are shown forpattern[matchesSymbol]
, and it's really only mentioned once, seemingly in passing, after which the focus is immediately moved to something completely different.Some more explanation, and maybe an example, would be nice there.
( Why do we need to work with
pattern[something]
instead of just withpattern
?!? )That part is supposed to be implying that the solution needs to define properties on one or more prototypes (with the
matchesSymbol
as the key) - thepattern
arguments are just ordinary values,pattern[matchesSymbol]
won't exist if the solution doesn't define it.I agree that part is a little unclear and I'll have a think about how to improve the explanation.
It's to enable special handling for custom types. E.g. if I made a
Point
class (storing a 2D coordinate) and then tried to use aPoint(_, _)
pattern, it wouldn't work by default, but I could override[matchesSymbol]
and implement logic that made it work.I've reworked the description, added an example of overriding
[matchesSymbol]
, and added the following to the initial solution:What do you think?
How does a primitive value match to an
Object
?If you mean "How does
1
match to{}
?", it doesn't (the match will never succeed). If you mean "How does1
match tonew Number(1)
?", it unwraps theNumber
and matches successfully (i.e.1
matches1
).I meant input
1
to pattern{}
.1
is notnull
orundefined
, so that should match, no?1
is a primitive, not anObject
, but the same goes fornull
andundefined
.Correct.
{}
matches1
, but notnull
orundefined
.I've drawn the distinction this way because
1
can act like an object in a way thatnull
andundefined
can't. Specifically,(1).a
returnsundefined
, whilenull.a
andundefined.a
throw errors.Don't we have
null?.a
inNode 14
now? ( I'm not yet used to that being available now either. )It seemed like specifically mentioning and not mentioning certain things in the descriptions implied things, and those implications seemed to be incorrect or inconsistent.
True (and
null?.['a']
, which I just found out about), but I still think it would be strange for{}
to matchnull
.I think it's worth mentioning that
{ a: undefined }
should match1
, even though1
doesn't have ana
property, because1['a']
isundefined
. When matching againstnull
there has to be some kind of check to avoid trying to access a property, and I thought the right course of action would be to detectnull
and say that the pattern doesn't match. It would be strange to have all object literal patterns not matchnull
except for{}
(for which the pattern matching never needs to access a property, so never needs to check fornull
before doing so), so I added the rule that{}
can't matchnull
/undefined
.Could you elaborate on this and maybe I can clean up the description some more?
I've reworked the description some more, so hopefully things are clearer now. The object explanation is now:
Treating constructors as normal functions introduces all sorts of weird behaviour, including
Error
s that have to be caught and ignored.Encoding
is Boolean
asBoolean
seems pretty at first glance, but this is not how JS contructors work, and the consequences are dire. Unfortunately, there is no way to distinguish constructors and non-constructor function, but surely some indication to test for either presence in the prototype or return value when applied can be defined, and would make this part of testing much more elegant.My reasoning here is that a properly designed constructor will "do no work", so it should be safe to assume the
Error
s are input validation errors that can be ignored.If you mean "they have no side effects", no, almost nothing should. But they will return their argument turned into a valid whatever it is they make, and that will almost always be truthy.
The real problem is we don't know when to handle a function as a constructor, so we're condemned to do special casing for a certain list of functions (
Boolean
and others ), and this list can also never accommodate custom constructors.This is handled by requiring an invoked function to return
true
rather than something truthy. This makesBoolean
the only"normal"special case.It would theoretically be possible to define a custom class that needed special treatment likeBoolean
, but it wouldn't be a normal thing to do and the issue could be resolved by defining a[matchesSymbol]
override for the constructor function of the new class.(Pretty sure there is actually no way to define another special case class, at least as long as the solution uses
===
.)I realized the description incorrectly implied there was more than one exception to consider ("An exception to this rule is the
Boolean
function...").In the context of the default rules,Boolean
is the only exception, so I've updated the description to reflect that.Normal JavaScript behaviour is
NaN !== NaN
.NaN
is not equal to anything, even itself.I understand the temptation, but please do not redefine this normal, expected JavaScript behaviour.
My view here is that pattern matching and an equality check are not the same thing (an equality check can be used for a pattern match, but that's an implementation detail), so it's OK to deviate from JavaScript's (widely regarded as awful) standard behaviors.
I understand somewhat where you're coming from, but this behaviour is not awful - it's specified, documented, consistent behaviour imported from IEEE-754. ( Certain other JS quirks are not consistent, and completely self-imposed. )
But, fair point about pattern matching not being equality checks.
It leads to some godawful ugly implementation though, because what we have to work with is equality checks. :/