Skip to content
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

Lang: partial operator #402

Open
ospencer opened this issue Oct 22, 2020 · 7 comments · May be fixed by #2091
Open

Lang: partial operator #402

ospencer opened this issue Oct 22, 2020 · 7 comments · May be fixed by #2091
Labels

Comments

@ospencer
Copy link
Member

The partial operator allows you to partially apply a function.

For example, look at this implementation of an add function:

let add = (a, b) => a + b

In let add2 = partial add(2), add2 is a function that accepts a single argument and adds two to it.

Arguments may also be skipped and provided later:

let map123 = partial List.map(_, [1, 2, 3])

map123(a => a * 4) // [4, 8, 12]
@cullophid
Copy link

Do you need the partial keyword?

@ospencer
Copy link
Member Author

ospencer commented Nov 8, 2020

@cullophid There's no technical reason why the keyword is necessary. We feel that when reading code in languages that allow partial application, it's not at all obvious that a function is being partially applied. You only really know that a partial application is happening if you know the type of the function being called.

We wanted something more explicit in Grain, and the partial keyword is what we came up with. If we find that it's burdensome, we'll add syntax for it. This isn't at all the proposed syntax, but it could look similar to invoking macros in Rust: add!(1)

@bmakuh
Copy link
Contributor

bmakuh commented Nov 17, 2020

I'm curious about this; while I agree that reading through code and coming across a partially-applied function can be a bit of a brain-bender the first time you see it, I do wonder if having a partial keyword is perhaps not quite the right solution. In JavaScript (to use a common example) it can be weird to see something like onClick: handleClick when you know that the handleClick function actually takes arguments. You could envision someone asking, "Shouldn't this be onClick: (event) => handleClick(event)?"

Partial application feels similar to me: if a beginner came across code like

"foo bar baz"
 |> String.split(" ")
 |> Array.join(",")

That beginner could reasonably ask questions like, "Wait, doesn't String.split take two arguments? Which string is being split?" That's actually a good question, and beginners should ask it, but I don't think it implies that languages should be more verbose. For example, The following code would still provoke questions, I imagine:

"foo bar baz"
 |> partial String.split(" ")
 |> partial Array.join(",")

The beginner might now ask, "What does partial mean? And doesn't String.split take two arguments? Which string is being split?" In other words, I don't think the keyword actually solves education problem. What if instead we tried to solve that problem with a two-pronged approach of helpful compiler errors and good dev tooling? TypeScript does a really good job with things like this: if you try to call a function that returns a promise, tsc will ask, "Hey did you mean to put an await here? Your code doesn't make sense without it." If someone is trying to use a function that isn't fully applied yet, you could raise an error like

Hey, you wrote
let x = 3 / add(1)

but `add(1)` is of type (number) => number while the expression is expecting a value of type Number. 
Did you forget to fully apply the `add(1)` function? If you're unfamiliar with the concept of partial 
application, read more here <link to helpful guide in documentation about partial application in Grain>

Anyway, just my two cents. 😄

@ospencer
Copy link
Member Author

Thanks for the feedback! 😄

In the cases where you're familiar with the functions being partially applied, I completely agree with you. If you're not familiar with the functions, say, in user code rather than standard library functions, it's really hard to tell that partial application is happening. A benefit of the partial keyword is that it gives you something to search for—you're not sure what it means, but you can now look up partial in the Grain docs, or with programming in general.

I'm all for helpful errors and good tooling though! OCaml has a form of this, where they warn if you have a statement that doesn't return unit. That works pretty well, but you miss out on the warning if you ignore a function call—if parameters are added, your function call just doesn't happen. I haven't seen a solution for this, but I suppose we could look out for that exact use case.

We really like how explicit partial is. We get that it's more to type, but we think the explicitness of it outweighs the extra characters.

I should have a draft PR up pretty soon so we can all play around with it and see how it feels. If we hate it, we can remove the keyword!

@bmakuh
Copy link
Contributor

bmakuh commented Nov 19, 2020

Sounds good. A draft PR with the ability to play with the syntax would be a good idea, and I definitely agree that the "google-ability" factor is higher with a keyword rather than the implicit approach!

@EduardoRFS
Copy link
Contributor

EduardoRFS commented Oct 8, 2021

I actually like the idea of a partial keyword, as you can easily have a "education" mode where by hovering the keyword you get a link on your IDE to the documentation.

And an interesting thing to think about always requiring the partial keyword, is that it makes clear where all the missing parameters will be collected.

let f = List.map(x => add(x, _), _)
/* probably means */
let f = z => List.map(x => y => add(x, y), z)
/* but you likely want want */
let f = y => z => List.map(x => add(x, y), z)

/* by having the explicit partial there is no ambiguity */
let f = partial List.map(x => add(x, _), _)
/* means */
let f = y => z => List.map(x => add(x, y), z)

/* and to achieve the naive transformation */
let f = partial List.map(x => partial add(x, _), _)
/* means */
let f = z => List.map(x => y => add(x, y), z)

@phated
Copy link
Member

phated commented Oct 9, 2021

I'd have to dig up the conversation in discord, but I believe @peblair wanted to always have the _ placeholders.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants