question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Suggestion on inline usage, updates, simple records and "_" prop as a default

See original GitHub issue

I started to use unionize at work for modelling redux states. So far it is a great conceptual fit however we miss several features/syntax 😃

Usually it looks similar to this.

const State = unionize({
  Loading: ofType<{}>(), // note there is no payload
  Loaded: ofType<{ data: string }>(),
  Error: ofType<{ err: string }>()
});

Pattern matching

So far we have way more inline usage of matching (when there is an object to match with right away). default syntax is a bit verbose ()=>{…} payload for default case is not the initial object (useful for updates)

Suggestion

const val = State.Loaded({ data: 'some data' });
const str = State.match(val, { // note that val is a fist arg here
  Loaded: ({ data }) => data,
  _: v => 'default' // default, v === val here
});

“_” is a reserved prop for default case. Which is aligned with other languages + probably won’t be used (vs “def” or “default”)

In summary

  • “_” is a fallback case + the original object is passed as payload so it is possible to write {_:identity} where identity = x=>x

  • inline usage vs curried. Maybe another func name: matchInl, matchI or even switch 😃

First class support for no payload cases

const State = unionize({
  Loading: simple(), // need a better name though, "noArgs" maybe?
  Loaded: ofType<{ data: string }>(),
  Error: ofType<{ err: string }>()
});

const loading = State.Loading(); // note no args to create it.
//So it can even be memoized (return the same object all the time)

Immutable update

this comes up a lot with dealing with redux. I need to modify data only if state is loaded.

const updatedVal = State.update(val, {
  Loaded: ({ data }) => ({ data: data + ' yay!' })
});

Note:

  • update signature: update:: State -> State.
  • If Loaded case would have a different shape ofType<{data: string, count: number}> nothing would change. So Loaded case has a signature of Loaded:: Loaded -> Partial Loaded (similar to react’s “setState”).
  • There is no need to provide Error and Loading cases. The same value is returned if there is no match. Functionally will be similar to prism.

config object as an argument

I know that in ur example it was used to represent redux actions but writing

const Action = unionize({
  ADD_TODO:  ofType<{ id: string; text: string }>(),
}, 'type', 'payload');

const action = Action.ADD_TODO({...})

is a bit tedious.

suggestion

const Todos = unionize(
  { Add: ofType<{ id: string; text: string }>() },
  { tag: 'type', payload: 'payload', prefix: '[TODOS] ' }
);

type Config = { tag?: string; payload?: string; prefix?: string };

//so you can simplify creating new actions
const namespace = (prefix: string) => ({ tag: 'type', payload: 'payload', prefix })

const Todos = unionize(
  { Add: ofType<{ id: string; text: string }>() },
  namespace( '[TODOS] ')
);

type AddType = typeof Todos._Record.Add
// {tag: "[TODOS] Add", payload: {id: string; text: string}}

I know this is a lot of suggestions but I decided to reach out to see if they make sense to you 😃

And thanks for an amazing library!

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:7 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
twopcommented, Mar 23, 2018

I actually was going to start working on “my own version” today… started to get hopeless 😃 But if you agree with the spirit of the proposals above I would be happy to contribute!

Sorry, been a busy week! And I wanted to have time to think carefully about everything you wrote. As you said, it’s a lot of suggestions 😃

I would want to be aligned on api surface first though. Questions that came to my mind:

  1. For inline matching do we use a different keyword? “switch” jumps to my mind (I wanted to name my lib something like “ts-enum” and model it after swift’s enum). If we use just “match” is it going to be hard to determine at runtime (I’m not in expert in ts)? Does this magic involve checking how many args were passed in?

We should just overload match IMO. The “magic” is just checking anything you could check at runtime in JS… in this case yes, it’s sufficient to check the number of arguments, as long as the default case change (1.1) happens first.

1.1 “_” vs “default” vs “def” vs “_default”. I would say default is the most intuitive but more likely to be taken vs others. I would propose “_” or “default”.

const str = State.match({ 
  Loaded: ({ data }) => data,
  _: v => 'default' });
// vs 
const str = State.match({ 
  Loaded: ({ data }) => data,
  default: v => 'default' });
// vs 
const str = State.match({ 
  Loaded: ({ data }) => data,
  _def: v => 'default' });

default would be my strong preference, because it’s completely analogous to the semantics of default in switch statements.

  1. btw I like “Loading:{}” for simple cases. This one has to wait for ts 2.8 right?

No, that’s been possible forever. It works because all ofType<T>() does is give you something (erroneously) of type T. But {} by itself is already of type {}, so you never need to write ofType<{}>().

  1. Immutable update should be curried or inline? Or both? If both the first question applies here as well (At work I need inline). + There is an alternative syntax for that:
const updatedVal = State.update(val, {
  Loaded: ({ data }) => ({ data: data + ' yay!' })
});
// vs
const updatedVal = State.upd.Loaded(val, ({ data }) => ({ data: data + ' yay!' }));
// So accept any of the states but execute update only in Loaded case

I personally feel that the first one is more generic and intuitive but still wanted to bring that up.

It would be nice and consistent for it to be overloaded in the same way as match.

  1. Config object is potentially a breaking change. It should easy to create an overload through string vs object arguments (again not an expert in ts).

Your default case syntax (1.1) is already a breaking change, and less avoidably so, so I think we should just embrace it and make this a breaking change as well. It’s a better API, so we should just go with it and not try to maintain backwards compatibility.

If all 3 make sense to you (except 2) would you rather have them as a single PR or 3 separate ones?

Separate PRs would be preferable. As I noted you’d probably want to implement (1.1) before (1) and (3) because it makes disambiguating the overloads as simple as checking the number of arguments.

1reaction
pelotomcommented, Mar 23, 2018

Pattern matching

So far we have way more inline usage of matching (when there is an object to match with right away). default syntax is a bit verbose ()=>{…}

I would be open to adding an overload that lets you pass the object to be matched first. I do find lots of uses for passing the curried match function to a HOF though, so I definitely want to keep that as an option.

payload for default case is not the initial object (useful for updates)

I guess I don’t see any harm in adding this. I usually feel like it’s a bit superfluous to pass someone an object they already clearly have in their possession, since they passed it to you, but I can see that it’s useful if you’re passing an expression to the match function.

Suggestion

const val = State.Loaded({ data: 'some data' });
const str = State.match(val, { // note that val is a fist arg here
  Loaded: ({ data }) => data,
  _: v => 'default' // default, v === val here
});

“_” is a reserved prop for default case. Which is aligned with other languages + probably won’t be used (vs “def” or “default”)

I normally shy away from reserving words, but I think this is fine, and would make it easier to disambiguate the overloads due to the first suggestion. Although I’d probably opt for default instead of _.

First class support for no payload cases

const State = unionize({
  Loading: simple(), // need a better name though, "noArgs" maybe?
  Loaded: ofType<{ data: string }>(),
  Error: ofType<{ err: string }>()
});

I would just use Loading: {}, why does it need to be more complicated? In the case of “unit” types, there’s no need to use the fakery of ofType<...>(), you can just provide a literal value of the type.

const loading = State.Loading(); // note no args to create it.
//So it can even be memoized (return the same object all the time)

There was a PR to accomplish this but it was deferred until 2.8 lands. Actually, with conditional types it might even be possible to make it not a function at all, just a value State.Loading

Immutable update

I really like it! 👍

config object as an argument

Also like it!

I know this is a lot of suggestions but I decided to reach out to see if they make sense to you 😃

And thanks for an amazing library!

Thanks for the suggestions, I think there are a lot of good ideas here. I’m quite busy at the moment so I can’t promise when I’ll have time to get to them, but I would certainly entertain PRs 😄

Read more comments on GitHub >

github_iconTop Results From Across the Web

Suggest changes - GitLab Docs
Introduced in GitLab 12.7. GitLab uses a default commit message when applying suggestions: Apply %{suggestions_count} suggestion(s) to %{files_count} file(s).
Read more >
System update sets - Product Documentation | ServiceNow
Manage how update sets store, retrieve, preview, and apply configuration changes between instances. Update set use. These procedures help you ...
Read more >
Set-PSReadLineOption (PSReadLine) - PowerShell
Specifies the source for PSReadLine to get predictive suggestions. Valid values are: None - disable the predictive IntelliSense feature (default). History - ...
Read more >
Props typing in Vue.js 3 with TypeScript - Stack Overflow
You should use it with PropType imported from vue like Object as PropType<FlashInterface> :
Read more >
Input Components - React-admin - Marmelab
Such components allow to update a record field and are common in the ... Tip: Use the sx prop rather than className to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found