DanielXMoore / Civet

A TypeScript superset that favors more types and less typing
https://civet.dev
MIT License
1.38k stars 29 forks source link

Add support for pipe operator, function currying, and partials #75

Closed francescoagati closed 5 months ago

francescoagati commented 1 year ago

Livescript support piping partials and curry function. Should be nice have this in Civet

STRd6 commented 1 year ago

Thanks for the suggestions!

We'll probably add the |> pipe operator at some point.

I don't think we'll add LiveScript's operators as functions, instead we have & function syntax which covers a lot of that same need.

list.map &+1
people.map &.name
---
list.map($=>$+1)
people.map($=>$.name)

I also don't think we'll add --> curried functions but I'm open to be convinced with examples.

edemaine commented 1 year ago

We now have the pipe operator |>, but I think we're still lacking in what are called placeholders in the TC39 proposal, which I think is a natural way to support partials/currying. Copying from https://github.com/DanielXMoore/Civet/pull/83#issuecomment-1364739339 :

I imagine it's really common to want to pass the piped value in as a first argument to a function, with other arguments given explicitly. What we want is shorthand for ($) => func($, foo, bar).

Not sure what's best, but it kind of highlights the limitation of the current & notation... yet it feels like we're close.

STRd6 commented 1 year ago

I think using explicit arrow functions as a way to re-order arguments or specify a local alias is good. That way you can add type annotations and make use of destructuring and everything should compose fine. This way we can combine with existing language features rather than making special purpose syntaxes for every situation.

x + 1 |> ($) => $ * $
credentials
|> login
|> getUser
|> ({id}) => register id, 1
sultan99 commented 1 year ago

I have noticed the -> is taken for the function declaration. It could be great if it were for curried functions. Just an example:

fun add(x::number, y::number)
  x + y

// equals ๐Ÿ‘‡
function add(x: number, y: number) {
  return x + y
}

add := (x:: number, y:: number) => x + y
// equals ๐Ÿ‘‡
const add = (x: number, y: number) => x + y

add := (x:: number, y:: number) -> x + y
// equals ๐Ÿ‘‡
const add = curry((x: number, y: number) => x + y)

// ๐Ÿง‚add can be called:
add(1, 2)
add(1)(2)

// usage
input := 4

// pipe version
input
  |> add 3
  |> subtract 2
  |> console.log

// function composition
compute := compose(
  console.log
  minus 2
  add 3
)

compute input

// prints ๐Ÿ‘‰ 5

Or maybe we can use '>->' ?

image

Many monospaced fonts have that array in ligatures.

STRd6 commented 1 year ago

@sultan99 Thanks that's a great example for how curried functions can combine with the pipe operator.

I think using LiveScript's --> or your suggestion of >-> for a curried function declaration is something we could add.

sultan99 commented 1 year ago

@STRd6, any of these can be!

>-> & --> monolisa & fira fonts have them:

image
gustavopch commented 1 year ago

What about ~>?

STRd6 commented 1 year ago

What about ~>?

Ideally whichever we choose would have both ==> and --> versions to correspond to the two ES flavors of functions.

sultan99 commented 1 year ago

I have checked the ligatures, and we have all of them!

== === != !== > --> => ==> ~> >-> ๐Ÿ‘‡

image

I think ==> & --> are the best:

add := (x:: number, y:: number) ==> x + y
// equals ๐Ÿ‘‡
const add = curry((x: number, y: number) => x + y)

add := (x:: number, y:: number) --> x + y
// equals ๐Ÿ‘‡
const add = curry(
  function(x: number, y: number) {
    return x + y
  }
)

In the second example, we can use this. Not sure why but anyway:

add := (x:: number, y:: number) -->
  console.log(@.name)
  x + y
sultan99 commented 1 year ago

Guys, it would be sexy syntax!

toJson := res => res.toJson()
pick := (key:: keyof T, obj:: T) ==> obj[key]

id := 123
fetch 'api/users/:#{id}'
  .then toJson
  .catch pick 'message'
  .finally console.log
orenelbaum commented 1 year ago

If we're using the ==> operator, why do we need the :: instead of normal :? As for the compilation output, not sure you need a utility function, you could just compile it this way

add := (x:: number, y:: number) ==> x + y
const add = (x: number) => (y: number) => x + y

add := (x:: number, y:: number) --> x + y
const add = (x: number) => function (y: number) { return x + y }
sultan99 commented 1 year ago

I have seen :: in the discord chat as a suggestion to use it for typing. I'm not familiar with syntax yet and I'm still confusing proposals with real syntax. Sorry...

gustavopch commented 1 year ago

@sultan99 The :: is for https://github.com/DanielXMoore/Civet/discussions/126, where we plan to provide a more concise way to destructure and type parameters at the same time.

kabo commented 1 year ago

Not sure if this is the right ticket for what I'm after, let me know if not and I'll create a new ticket instead.

I use ramda a lot and I'm curious if civet could support a few things natively. Something I do a lot with ramda is create a function using pipe. For example:

const doTheThing = R.pipe(
  R.pluck('myprop'),
  R.map(R.toUpper)
)

console.log(doTheThing([{myprop: 'hello'}])) // outputs [HELLO]

My attempt at doing this with civet

input := [myprop: 'hello']
pluck := (prop) => .map .`${prop}`

// works
input
  |> pluck 'myprop'
  |> .map .toUpperCase() 
  |> console.log

// doesn't do what I want it to do
doTheThing := pluck 'myprop' |> .map .toUpperCase()

// doesn't work either, but in a different way
doTheThing2 := .map .myprop |> .map .toUpperCase()

console.log doTheThing2 input

doTheThing comes out as

const doTheThing = pluck("myprop").map(($3) =>
  $3.toUpperCase()
);

// as the first thing in the pipe is a function I would have wanted
const doTheThing = ($) => pluck("myprop")($).map(($3) =>
  $3.toUpperCase()
);

doTheThing2 comes out as

const doTheThing2 = (($4) =>
  $4.map(($5) => $5.myprop)).map(($6) =>
  $6.toUpperCase()
);

// I would have wanted (note one less paren after $5.myprop, it's added to the last line instead
const doTheThing2 = (($4) =>
  $4.map(($5) => $5.myprop).map(($6) =>
  $6.toUpperCase()
));

playground link

Or perhaps I'm just using it wrong, perhaps the pipe operator |> is only intended to be used when there's a start value, and there's something like fp-ts flow operator that I'm missing? Pipe vs flow in fp-ts.

orenelbaum commented 1 year ago

the operator works only on a start value, you are looking for a function composition operator (flow in fp-ts) which we discussed a bit in the Discord.

STRd6 commented 1 year ago

@kabo this might work but I agree that it would be nice to have a cleaner syntax for function composition kinds of things.

doTheThing := ($) => (pluck 'myprop' |> .map .toUpperCase())($)
kabo commented 1 year ago

ok, so I can go ahead and create a ticket for a function composition / flow operator?

STRd6 commented 1 year ago

@kabo Sounds good. I don't think we have another issue specifically for composition / flow yet. There are some discussions and maybe an issue that are related but we can link them in later.

Thanks for the detailed report and clear description!

danielbayley commented 8 months ago

I think ==> & --> are the best:

@sultan99 Agreed, out of thoseโ€ฆ There is also =>> and ->>, which havenโ€™t been mentioned, and I think visually express currying quite wellโ€ฆ also have ligatures. cc: @STRd6 @edemaine

edemaine commented 5 months ago

Closing this as: