reasonml-q-a icon indicating copy to clipboard operation
reasonml-q-a copied to clipboard

What does _ and ~ mean?

Open zth opened this issue 4 years ago • 9 comments

What does _ and ~ mean?

zth avatar Feb 14 '20 19:02 zth

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.

baransu avatar Feb 15 '20 22:02 baransu

cc @mrispoli24

zth avatar Feb 16 '20 07:02 zth

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.

mrispoli24 avatar Feb 17 '20 15:02 mrispoli24

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 names

The 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 BuckleScript

I 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.

zth avatar Feb 17 '20 20:02 zth

@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:

zth avatar Feb 17 '20 20:02 zth

_ as a convention for using reserved names

When 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 avatar Feb 17 '20 20:02 baransu

@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:

zth avatar Feb 17 '20 20:02 zth

_ as an argument placeholder in BuckleScript

When arguments order is not always ideal here comes _ placeholder.

Using with pipe-last optimized functions

// using pipe-last
[|1, 2, 3|] |> Array.map(x => x * 2);
// using pipe-first
[|1, 2, 3|]->Array.map(x => x * 2, _);

Using with pipe-first optimized functions

// using pipe-first 
[|1, 2, 3|]->Belt.Array.map(x => x * 2);
// using pipe-last
[|1, 2, 3|] |> Belt.Array.map(_, x => x * 2);

Using with not first nor last argument

[1, 2, 3]->mySuperComplexFunction(arg1, _, arg2, arg3)
// is equal to
mySuperComplexFunction(arg1, [1, 2, 3], arg2, arg3)

Using as with labeled argument

[1, 2, 3]->mySuperComplexFunction(~labeled=_, notLabeled)
// is equal to
mySuperComplexFunction(~labeled=[1, 2, 3], notLabeled)

Using with currying

let multiplyByTwo = Belt.Array.map(_, x => x * 2)
// is equal to
let multiplyByTwo = array => Belt.Array.map(array, x => x * 2)

baransu avatar Feb 17 '20 20:02 baransu

Adding to @baransu 's answer.

_ as a convention for using reserved names

This is often called name mangling. The old Bucklescript manual describes the applied rules:

  1. If __[rest] appears in the label, index from the right to left.
  • If index = 0, nothing mangled
  • If index > 0, __[rest] is dropped
  1. Else 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

woeps avatar Feb 18 '20 21:02 woeps