{"action":"created","issue":{"url":"https://api.github.com/repos/pelotom/runtypes/issues/36","repository_url":"https://api.github.com/repos/pelotom/runtypes","labels_url":"https://api.github.com/repos/pelotom/runtypes/issues/36/labels{/name}","comments_url":"https://api.github.com/repos/pelotom/runtypes/issues/36/comments","events_url":"https://api.github.com/repos/pelotom/runtypes/issues/36/events","html_url":"https://github.com/pelotom/runtypes/issues/36","id":293442189,"number":36,"title":"Suggestions around pattern matching","user":{"login":"twop","id":6633216,"avatar_url":"https://avatars1.githubusercontent.com/u/6633216?v=4","gravatar_id":"","url":"https://api.github.com/users/twop","html_url":"https://github.com/twop","followers_url":"https://api.github.com/users/twop/followers","following_url":"https://api.github.com/users/twop/following{/other_user}","gists_url":"https://api.github.com/users/twop/gists{/gist_id}","starred_url":"https://api.github.com/users/twop/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twop/subscriptions","organizations_url":"https://api.github.com/users/twop/orgs","repos_url":"https://api.github.com/users/twop/repos","events_url":"https://api.github.com/users/twop/events{/privacy}","received_events_url":"https://api.github.com/users/twop/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2018-02-01T08:26:31Z","updated_at":"2018-02-01T22:53:58Z","closed_at":null,"author_association":"NONE","body":"First of all, thank you for putting such an amazing library. It utilizes both typescript and javascript really well, producing such an empowering and comprehensive developer experience :)\r\n\r\nI found your project while looking at the ways to pattern match in typescript. So my suggestions are biased.\r\n\r\nLet's say we have this:\r\n\r\n```typescript\r\nconst One = Record({ tag: Literal('one'), descOfOne: String });\r\nconst Two = Record({ tag: Literal('two'), descOfTwo: String });\r\n\r\ntype OneOrTwo = Static<typeof One> | Static<typeof Two>;\r\n```\r\n\r\nWhile matching I want to extract payload as well. Something like:\r\n\r\n\r\n```typescript\r\nconst DescriptionOfOne = One.pipe(x=>x.descOfOne); \r\n//not Runtype anymore, can be used only for pattern matching\r\n\r\n// OneOrTwo | null -> string \r\nconst getDescription = matchCase<OneOrTwo | null>()\r\n  .of(DescriptionOfOne, desc => desc) // acceptes extracted prop\r\n  .of(Two, ({descOfTwo}) => descOfTwo) // normal types can be used just fine\r\n  .of(Void, ()=> 'nothing');\r\n```\r\n\r\nDescriptionOfOne has different type now. Lets say Pattern<One, string>.\r\n\r\n```typescript\r\nconst NonEmptyDesc = DescriptionOfOne.withContraint( desc => desc.lenght >0) \r\n// Error! Will not compile. Patterns can only be mapped over.\r\n\r\nconst OneDescLenght = DescriptionOfOne.map(decs => desc.lenght);\r\n// totally valid though.\r\n```\r\n\r\nI think it makes sense to map them further to produce different pipelines.\r\n\r\nBut if we use Records extensively for matching we have perf issues :( \"check\" checks all props recursively.\r\n\r\n```typescript\r\nconst TaggedOne = Tag(One, 'tag'); //new Runtype that remembers which one is the tag\r\n// disables deep tree matching and checks only tags. \r\n// useful for application types (like redux actions)\r\n// It might make sense to introduce Tagged({shape}) and TaggedLiteral('one') for that purpose as well.\r\n```\r\n\r\nAlso with typescript 2.8 we will have a proper Diff<T,U>. And if I understand it correctly it opens this\r\n```typescript\r\nconst EmptyOne = One.withConstraint( one => one.descOfOne.length === 0);\r\n\r\n//Possible from typescript 2.8 with a help of Diff\r\nconst getDescription = matchCase<OneOrTwo | null>()\r\n  .of(EmptyOne, _ => 'empty one') // leaves the same type\r\n  .guard(One, _ => \"one\") // removes One, so after we can guard only \"Two | Void\"\r\n  .guard(Two, _ => \"two\") // leaves just \"Void\" to check\r\n  .guard(Void, ()=> 'nothing'); // \"never\" after this point\r\n  .exhaustive() // can be applied only to \"never\". \r\n// So it should give compile time error if not all types are ruled out\r\n// .default(()=>'def val') same as of(Always, ()=>{}).\r\n```\r\n\r\nNote probably constrained runtypes cannot be guarded.  Otherwise PositiveNumber can rule out Number < 0. Note: guarding on non constrained Patterns should be totally fine.\r\n\r\n++ Side note:\r\nLooking through the sources I noticed that \"validate\" uses try/catch from \"check\" to produce boolean result. Was there a specific reason for that? I guess if we hit an if statement that \"validates\" to false often there might be significant overhead for try/catch as well as allocating a Result object.\r\nDoes it make sense to have validateInternal(): true | string, aka either passed or failed with message. And then \"check\" can throw with a proper message.\r\n\r\n+++ Side note n2:\r\nYou library is brilliant and inspiring. I felt that I love my job even more just thinking about these suggestions :)\r\n\r\nI would love to collaborate/contribute if you like these ideas "},"comment":{"url":"https://api.github.com/repos/pelotom/runtypes/issues/comments/362429437","html_url":"https://github.com/pelotom/runtypes/issues/36#issuecomment-362429437","issue_url":"https://api.github.com/repos/pelotom/runtypes/issues/36","id":362429437,"user":{"login":"pelotom","id":128019,"avatar_url":"https://avatars2.githubusercontent.com/u/128019?v=4","gravatar_id":"","url":"https://api.github.com/users/pelotom","html_url":"https://github.com/pelotom","followers_url":"https://api.github.com/users/pelotom/followers","following_url":"https://api.github.com/users/pelotom/following{/other_user}","gists_url":"https://api.github.com/users/pelotom/gists{/gist_id}","starred_url":"https://api.github.com/users/pelotom/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pelotom/subscriptions","organizations_url":"https://api.github.com/users/pelotom/orgs","repos_url":"https://api.github.com/users/pelotom/repos","events_url":"https://api.github.com/users/pelotom/events{/privacy}","received_events_url":"https://api.github.com/users/pelotom/received_events","type":"User","site_admin":false},"created_at":"2018-02-01T22:53:58Z","updated_at":"2018-02-01T22:53:58Z","author_association":"OWNER","body":"> Also with typescript 2.8 we will have a proper Diff<T,U>. And if I understand it correctly it opens this\r\n> ```typescript\r\n> const EmptyOne = One.withConstraint( one => one.descOfOne.length === 0);\r\n> \r\n> //Possible from typescript 2.8 with a help of Diff\r\n> const getDescription = matchCase<OneOrTwo | null>()\r\n>   .of(EmptyOne, _ => 'empty one') // leaves the same type\r\n>   .guard(One, _ => \"one\") // removes One, so after we can guard only \"Two | Void\"\r\n>   .guard(Two, _ => \"two\") // leaves just \"Void\" to check\r\n>   .guard(Void, ()=> 'nothing'); // \"never\" after this point\r\n>   .exhaustive() // can be applied only to \"never\". \r\n> // So it should give compile time error if not all types are ruled out\r\n> // .default(()=>'def val') same as of(Always, ()=>{}).\r\n> ```\r\n\r\nAgain, why not just\r\n\r\n```ts\r\nconst getDescription = match(\r\n  [EmptyOne, _ => 'empty one'],\r\n  [One, _ => 'one'],\r\n  [Two, _ => 'two'],\r\n  [Null, () => 'nothing'],\r\n  [\r\n    Always, // or however much you want to widen the input type beyond `OneOrTwo | null`\r\n    () => throw new Error('exhaustiveness check failed')\r\n  ],\r\n)\r\n```\r\n\r\nI'd really like to keep the core API surface area as small as possible while making sure all reasonable use cases are accounted for."}}
{"actor":{"display_login":"pelotom"}}