react-dream icon indicating copy to clipboard operation
react-dream copied to clipboard

Fix laws and simplify implementation

Open xaviervia opened this issue 7 years ago • 7 comments

.promap was not following Profunctor laws because .map was acting on the component as-if ReactDream was the identity Functor, without any extra insights into the type. This is now fixed, but the consequence is that there are two breaking changes 💥:

  1. .map acts on the result of running the component, so the React element it outputs.
  2. .promap’s second argument acts on the element as well.

Another effect of this is that there now no helper to apply regular higher-order components to the component inside the ReactDream. So we need a new one.

Since it is still an implementation of Functor, it could be called .map2. It’s not a fantastic name, but it’s accurate. It could probably have an alias. Quick alias brainstorm:

  • .hoc
  • .wrap
  • .update

Quick realization: it might be that HoCs need their own Fantasy Land type, since they do form a Category / Arrow.

xaviervia avatar Jul 28 '18 16:07 xaviervia

Right. The obvious name for the method is .enhance

xaviervia avatar Jul 28 '18 16:07 xaviervia

maybe you want to lift the component to another type where map works from Component to Component rather than from Element to Element?

a = ReactDream(props => </div>)
b = HOC(a)
b.map(hoc1).map(hoc2)

leostera avatar Jul 28 '18 16:07 leostera

Right. 🤦‍♂️ 🤦‍♀️ this is Applicative, but on the identity functor implementation of .map

xaviervia avatar Jul 28 '18 16:07 xaviervia

API plan update:

import ReactDream, { ReactBox } from 'react-dream'

ReactDream.Stateless(({ title }) => <h1>{title}</h1>)
  .map(element => <div>{element}</div>)
  .contramap(({ language }) => ({
    title: language === 'en' ? 'Hello' : 'Hola'
  }))
  .enhance(
    withState('updateTitle', 'title', 'Hola')
  )
  .name('Header')

ReactDream.Stateful(class extends Component {})
  .map() // not optimal!

ReactDream.Stateful(class extends Component {})
  .toStateless()
  .map() // optimal but verbose

const withChildren = (Parent, Up, Down) =>
  ReactDream.Stateless(({ parent, up, down }) =>
    <Parent.Component {...parent}>
      <Up {...up} />
      <Down {...down} />
    </Parent.Component>
  )

withChildren(
  Header,
  Title,
  Tagline
)
  .contramap()

// will build a Stateful
ReactDream(class extends Component {})

// will build a Stateless
ReactDream(props => <br />)

ReactDream(props => <hr />)
  .asBox()
  .map()

ReactBox(props => <hr />)
  .asDream()
  .map()

// Equivalences
ReactDream(x).enhance(f) == ReactDream(x).asBox().map(f).asDream()
ReactBox(x).map(f).asDream() == ReactDream(x).enhance(f)
ReactBox.of(f).ap(x) == ReactBox(x).map(f)
ReactBox.of(f).ap(ReactDream(x).asBox()) == ReactBox.of(f).apDream(ReactDream(x))

Keys:

  • ReactBox is introduced. ReactBox is an oh-so-slightly sugared Identity Functor. It is there just to be transformed from and to ReactDream, to provide Applicative for React Components.
  • .enhance is just .map of the Identity Functor. In reality, .enhance just jumps to ReactBox, maps, and then comes back.
  • The jump to ReactBox from ReactDream is done with asBox, and the jump back is done with asDream.
  • In the updated React Dream, Applicative laws don’t apply. Even if they did, it is not that useful. Instead, apDream will be added to the ReactBox so that lifted functions can easily be applied to ReactDreams, mimicking the old behavior of Applicative.

xaviervia avatar Aug 15 '18 20:08 xaviervia

I’m not sure about the names .asDream and .asBox. toDream and toBox might be better ones.

xaviervia avatar Aug 15 '18 20:08 xaviervia

Change roadmap:

  • [x] Kill current .ap
  • [x] Kill current .of
  • [ ] Introduce Stateless and Stateful constructors with proper inspect
  • [ ] Introduce .match on Stateless and on Stateful
  • [ ] Introduce .toStateless to Stateless and Stateful. In stateful, it just wraps with a function component. .toStateless(displayName: String) takes the parameter that will set a new display name
  • [ ] Reimplement .map for the Stateless and Stateful
  • [ ] Reimplement .contramap for the Stateless and Stateful
  • [ ] Reimplement .promap for the Stateless and Stateful
  • [ ] Reimplement .concat for the Stateless and Stateful
  • [ ] Reimplement .chain for the Stateless and Stateful
  • [ ] Introduce ReactBox
  • [ ] Introduce .map to ReactBox
  • [ ] Introduce .of and .ap to ReactBox
  • [ ] Introduce .toDream to ReactBox
  • [ ] Introduce .toBox to ReactDream
  • [ ] Introduce .apDream to ReactBox
  • [ ] Introduce .enhance to ReactDream

xaviervia avatar Aug 16 '18 16:08 xaviervia

And for the next iteration:

  • [ ] Create package for non-algebraic functions
  • [ ] Remove non-algebraic functions

xaviervia avatar Aug 21 '18 21:08 xaviervia