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

Enhancement: Eq<A>.equals curried by default for more ergonomic use #1284

Closed
kylegoetz opened this issue Jul 30, 2020 · 8 comments
Closed

Enhancement: Eq<A>.equals curried by default for more ergonomic use #1284

kylegoetz opened this issue Jul 30, 2020 · 8 comments
Milestone

Comments

@kylegoetz
Copy link

Right now if I am doing a filter-type operation, the predicate type takes one param and returns boolean.

Eq<A>.equals takes two params (items to be compared) and returns boolean.

So using equals as the predicate for a filter-type operation, I do as such:

const itemToCompare = {}
const itemList = [{}, {}, {}]
itemList.filter(i => e.equals(i, itemToCompare))

If instead Eq<A>.equals were written to be

interface Eq<A> {
  equals: (a:A) => (b:A) => boolean
}

then you could write

const itemToCompare = {}
const itemList = [{}, {}, {}]
itemList.filter(e.equals(itemToCompare))

Small thing, but I've run into it a few times and thought the code would be more readable if equals were curried like this.

@kylegoetz kylegoetz changed the title Eq<A>.equals curried by default for more ergonomic use Enhancement: Eq<A>.equals curried by default for more ergonomic use Jul 30, 2020
@gcanti gcanti added this to the v3 milestone Jul 30, 2020
@enricopolanski
Copy link

I think the same should apply then to the compare method of the Ord typeclass.

@samhh
Copy link
Contributor

samhh commented Dec 5, 2020

Could anyone offer a reason not to curry absolutely everything by default? It opens up partial application, and if you don't want to partially apply then f(x)(y) is really no worse than f(x, y). Perhaps in some cases there could be type inference issues?

@kylegoetz
Copy link
Author

If we're going to go whole hog, then I'd like to point out that when you

import { map } from 'fp-ts/es6/TaskEither
import { taskEither as TE } from 'fp-ts'

these two functions do not have the same type signature. One is curried, and the other is not. This surprised me and has made for less ergonomic code when writing functions that take a monad mea and then use mea.map and mea.chain, for example.

Originally I was writing

const program = pipe(
    tryCatch(()=>someAsync(), handleError), 
    TE.map(res => res.getData()))

but wanted to inject the monad:

const program = (mea:MonadIO2<URI> & HasTryCatch<URI>) => pipe(
    mea.tryCatch(()=>someFunction(m), handleError),
    _ => mea.map(_, res => res.getData()))

@SRachamim
Copy link
Contributor

SRachamim commented Dec 6, 2020

I think it's better to stick to one invocation, and curry only the data structure for piping ergonomics. In this case, I think it's OK to curry the equals and compare functions, since they act on the data-structure.

P.S.
fp-ts has one more case for currying: typeclass as single first-invocation. I don't know what I think about that.

@raveclassic
Copy link
Collaborator

@samhh We went this road once in the times of [email protected]. At that moment however TypeScript didn't really play well with curried data-last notation and that lead to bad DX so we decided to revert it.
Now that we have a proper type-checking for such notation we could think about migrating to curried data-last version.

@SRachamim

typeclass as single first-invocation

Yeah, I suggested it to @gcanti. At the moment it is in the backlog for [email protected].

@gcanti
Copy link
Owner

gcanti commented Dec 14, 2020

When we are talking about Ord, Semigroup, etc... are we talking about currying or make the operation(s) data-last?

Current definition (pipe unfriendly)

export interface Ord<A> extends Eq<A> {
  readonly compare: (first: A, second: A) => Ordering
}

assert.deepStrictEqual(
  pipe(1, (x) => ordNumber.compare(x, 2)),
  -1
)

Currying (pipe unfriendly)

export interface Ord<A> extends Eq<A> {
  readonly compare: (first: A) => (second: A) => Ordering
}

assert.deepStrictEqual(
  pipe(1, (x) => ordNumber.compare(x)(2)),
  -1
)

Data-last (pipe friendly)

export interface Ord<A> extends Eq<A> {
  readonly compare: (second: A) => (first: A) => Ordering
}

assert.deepStrictEqual(pipe(1, ordNumber.compare(2)), -1)

@samhh
Copy link
Contributor

samhh commented Dec 14, 2020

Personally when I request or otherwise discuss currying, data-last is implied, so it's the third example there.

@gcanti
Copy link
Owner

gcanti commented Dec 15, 2020

Ok, closing in favour of #1216

@gcanti gcanti closed this as completed Dec 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants