Open zth opened 4 years ago
Could you specify what do you mean? In which context would you like to better understand _
and ~
. ~
only exist in one context - labeled arguments but _
can be applied in a lot of contexts - wildcard, unused argument, pipe first placeholder.
cc @mrispoli24
Hey @baransu to add more color I got the ~
figured out where this is labeled arguments. To add some description for those who land on this thread labeled arguments are sortof like passing an object to a function where they allow you to pass arguments in whatever order you want to a function and use them by name.
Now the _
still perplexes me a little bit and probably because like you said it can be used in a lot of contexts. One context I ran into was trying to make an input in jsx with a type
parameter.
For example: <input type="text" />
throws and error but <input _type="text" />
fixes the error due to type being a reserved keyword. This made sense to me so it appears in one context _
is being used to fix language collisions.
In another context I ran into it was passing the DOM event into a function:
<input _type="text" onChange={ _evt => Js.log(_evt)} />
In noticed that you cannot just rename _evt
to whatever you want as you could in javascript. I also was never able to do something like _evt.preventDefault()
which I found confusing as well. Which may be another question entirely.
Just getting the rules around all of the contexts and situations under which an _
will be used would be awesome like you mentioned pipe first placeholder which I haven't event gotten into yet.
So, there's a few things to note here. I'll do my best to explain _
:
_
as "I don't care about this"One meaning of _
is "I don't care about this". When you for example pattern match, you can use _
to indicate that "this value can be whatever":
switch (myTupleOfStuff) {
| (true, _) => "It's true!"
| (false, 0) => "It's false, but 0, so it's fine..."
| (false, _) => "It's false and not 0, RED ALERT! NOT FINE!!!"
};
In the snippet above, _
is essentially used to say that "this can be whatever, I don't care". So it means that in the first branch with the leading true
, the second parameter in the tuple can be anything at all (any int
in this case) for that branch to match. In the second branch, we're matching exactly false
and 0
, and in the last branch we're matching false
and anything again.
_
can also be used to tell the compiler that you don't intend to use a parameter, but you want to have some form of name for it anyway. This could be for documentation purposes, or any other reason. In that case, just like you've illustrated above, you prepend the variable name with an underscore. Check out this snippet:
type item = (string, int);
let x = ("Some label", 12);
let extractLabelText = item => {
let (label, _value) = item;
label;
};
This won't give a compiler warning even though you make no use of _value
and that should result in a warning. And that's because of the prepended _
.
If you really don't care about showing what type of value the second item in the tuple is here, you could also do let (label, _) = item;
, which is basically saying "something's there, but I don't care what it is because I won't use it".
Another example that's perhaps more illustrative is pattern matching on a list:
let myWeirdFunction = theList => {
switch(theList) {
| [_, _, _, _, "hello"] => true
| _ => false
};
This above will only match a list that's exactly 5 items long, and where the fifth item is "hello". So, we use _
for the other items in our pattern match, since we don't care about them.
_
as a convention for using reserved namesThe other thing you've stumbled upon is that _
is used as a way of using reserved key words in Reason. It's almost universally used that way when it's suffixed in an identifier. So, for example since and
is a reserved word, people use type someType = { and_: bool };
to be able to use the word, but in a legal way.
To my knowledge, this is only a convention. There's nothing automatic or enforced about it. In fact, some people use typ
instead of type_
for instance wanting to use the word "type". This is a bit advanced, but since the JavaScript you're interacting with require these properties to actually be named something that's illegal in Reason, what you typically do is something like this:
type myType = {
[@bs.as "and"] and_: bool
};
That directive, [@bs.as]
, tells BuckleScript to treat and_
as and
in the compiled output, which means it'll work as expected. This is how your <input />
example works above, the props for that component is probably defined something like [@bs.as "type"] type_: string
.
_
as an argument placeholder in BuckleScriptI don't really know much about this, so I'll let someone else comment here, but I think this is a use case for _
as well.
@mrispoli24 would you mind posting why _evt.preventDefault()
doesn't work as a separate question? I think that's something that's a bit hard to grasp first, because things work quite differently in Reason compared to JS here. It got me real bad when I was starting out at least :grin:
_
as a convention for using reserved namesWhen using [@bs.obj]
and [@react.component]
together with external
there is automatic stripping of _
prefix for example:
[@react.component]
external make: (~_type: string, ~children: React.element) => React.element = "SomeComponentToBind"
will result in _type
being resolved as type
on JS side.
@baransu wow, that's excellent, I had no idea about that! I was going to say I was confused by why _type
worked when it believed it should be type_
, and there's the answer :grin:
_
as an argument placeholder in BuckleScriptWhen arguments order is not always ideal here comes _
placeholder.
// using pipe-last
[|1, 2, 3|] |> Array.map(x => x * 2);
// using pipe-first
[|1, 2, 3|]->Array.map(x => x * 2, _);
// using pipe-first
[|1, 2, 3|]->Belt.Array.map(x => x * 2);
// using pipe-last
[|1, 2, 3|] |> Belt.Array.map(_, x => x * 2);
[1, 2, 3]->mySuperComplexFunction(arg1, _, arg2, arg3)
// is equal to
mySuperComplexFunction(arg1, [1, 2, 3], arg2, arg3)
[1, 2, 3]->mySuperComplexFunction(~labeled=_, notLabeled)
// is equal to
mySuperComplexFunction(~labeled=[1, 2, 3], notLabeled)
let multiplyByTwo = Belt.Array.map(_, x => x * 2)
// is equal to
let multiplyByTwo = array => Belt.Array.map(array, x => x * 2)
Adding to @baransu 's answer.
This is often called name mangling. The old Bucklescript manual describes the applied rules:
If
__[rest]
appears in the label, index from the right to left.
- If index = 0, nothing mangled
- If index > 0,
__[rest]
is droppedElse if
_
is the first char
- If the following char is not 'a' .. 'z', drop the first
_
- Else if the rest happens to be a keyword, drop the first
_
- Else, nothing mangled
What does
_
and~
mean?