zenika-open-source / immutadot

immutadot is a JavaScript library to deal with nested immutable structures.
https://immutadot.zenika.com
MIT License
178 stars 5 forks source link

PoC on new currying #298

Closed nlepage closed 6 years ago

nlepage commented 6 years ago

Issue : #297

codecov-io commented 6 years ago

Codecov Report

Merging #298 into dev will increase coverage by 0.01%. The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##              dev     #298      +/-   ##
==========================================
+ Coverage   99.15%   99.17%   +0.01%     
==========================================
  Files         105      106       +1     
  Lines         356      364       +8     
==========================================
+ Hits          353      361       +8     
  Misses          3        3
Impacted Files Coverage Ξ”
packages/immutadot/src/core/curry.js 100% <100%> (ΓΈ)

Continue to review full report at Codecov.

Legend - Click here to learn more Ξ” = absolute <relative> (impact), ΓΈ = not affected, ? = missing data Powered by Codecov. Last update dfb76d4...91316ca. Read the comment docs.

nlepage commented 6 years ago

@frinyvonnick Ready to review

EmrysMyrddin commented 6 years ago

After some thinking, perhaps it is possible to have only one function curry that doesn't require the two implementations curried and maybeCurried and that doesn't rely on this.

Something like this (haven't tested it) :

export function curry(fn, minArity = fn.length) {
  const curried = previousArgs => (...args) => {
    const allArgs = previousArgs.concat(args)

    if (allArgs.length >= minArity - 1) {
      const obj = this.args[allArgs.length - 1])
      return fn(obj, ...allArgs.slice(0, -1))
    }

    return curried(allArgs)
  }

  return curried([])
}
nlepage commented 6 years ago

@EmrysMyrddin What you're suggesting doesn't handle a non curried call, it always takes the last parameter for obj.

nlepage commented 6 years ago

I like the usage of bind and this as a context, this doesn't need any dynamic function creations.

I had created the maybeCurried in order to separate concerns:

I agree that mutating the context is not a good idea at all. The first time I had written it with destructuring between each bind, I don't know why I changed it, I'm going to revert it...

EmrysMyrddin commented 6 years ago

For me, the bind function generate a new function when called. If it was not the case, calling bind on a function could have unexpected behavior.

nlepage commented 6 years ago

I read bind's documentation, it clearly states that a new function is created. They are even talking about an exotic function object, whatever this means...

I tried to make some performance tests: https://jsperf.com/binded-vs-curried/1 These doesn't seem very reliable:

I find Firefox's result hard to believe, I suspect it is doing some shady optimization like "hey, he doesn't use the value, let's not compute it" and it can't do the same with bind... That's why I tried to send the value to a noop function, but it doesn't change anything.

Let me know what's the result on your computers.

Anyway I'm leaning towards rewriting without using bind.

frinyvonnick commented 6 years ago

bind is 11% slower on Chrome for me πŸ˜„

EmrysMyrddin commented 6 years ago

I have the same results : bind is 4% slower on chrome and 91% slower on firefox.

EmrysMyrddin commented 6 years ago

Ok finally I have tried to do it with only one function. Not sure if the result is better. At least it doesn't duplicate some behavior

export function curry(fn, minArity = fn.length) {
  const curried = (previousArgs = [], last = false) => (...args) => {
    // Short circuit if no curry is necessary
    if (previousArgs.length === 0 && args.length >= minArity) return fn(...args)

    if (last) {
      const [obj] = args
      return fn(obj, ...previousArgs)
    }

    const allArgs = previousArgs.concat(args)
    const nextLast = allArgs.length >= minArity - 1

    return curried(allArgs, nextLast)
  }

  return curried()
}
nlepage commented 6 years ago

OK, one lined it ! Just kidding... 2 lined it.

Usually I avoid using ternary in this kind of function. What do you think ? Should I use if returns instead ?

EmrysMyrddin commented 6 years ago

πŸ‘ Very nice !

frinyvonnick commented 6 years ago

I find it less readable ! I prefer the one with if and returns πŸ˜„