0

I'm trying to write some parsers in Typescript. My parsers are all instances of a base class, Parser, parametrized by types, A and S, representing the result of a parse and the parser state.

class Parser<A, S> {
  parse: (state: S) => Response<A, S>

  constructor(parse: (state: S) => Response<A, S>) {
    this.parse = parse

  // various combinator methods...
}

In several cases of interest to me, I'd like the state S to be adapted to parsing strings, and, also, to carrying some user-definable payload of type U. Thus, the S of interest might implement:

interface SS<U> {
   str: string;
   user: U
}

Then I'd like to construct the most basic parser I can think of: char. It consumes and returns the first character in the string being parsed, if there is one, or fails if there isn't. Thus, I'd like

char: Parser<string, SS<U>>

for any U.

But, alas, Typescript doesn't allow values involving generics, just functions, classes, etc. ["Variables can't have free generics", as Titian nicely puts it in the comments below.] phAre there any standard patterns to circumvent this limitation? The only "solutions" I can think of are:

  1. Make char into a function: function char<U>(): P<U> {...; return realChar}. I don't find this workaround appealing because there's a runtime cost every time I run char().

  2. Wrap all my parsers that depend on U in a function:

    function parsers<U>() {
        let char: Parser<string, SS<U>> = ...
        let letter: Parser<string, SS<U>> = ...
        let digit: Parser<string, SS<U>> = ...
        // other parsers
        return {char, digit, letter, ...}
    }
    

    Then I could instantiate them all by running parsers() when I use them and have a concrete U at hand.

Are there any other approaches that are more elegant or that incur minimal runtime overhead, or an established pattern? [Anyone know if anything like "values of generic type" (is there better terminology for this?) on the Typescript roadmap?]

fmg
  • 813
  • 8
  • 18
  • `char: Parser>` would be in an object literal ? – Titian Cernicova-Dragomir Oct 17 '18 at 22:21
  • No, I'd like to define `char` using an assignment of the type `char: Parser> = new Parser(state => ...)`, but this is illegal since a class instance cannot be of generic type. This is the limitation I'm seeking a workaround for. – fmg Oct 17 '18 at 22:32
  • 2
    Variables can't have free generics. How would you use `char` when would `U` be determined ? – Titian Cernicova-Dragomir Oct 17 '18 at 22:35
  • I'd would perhaps use it like `(char as SS).parse("abcde")`. I'd have to make sure to concretize `U` before invoking any methods on `char`, of course. (But, in my ideal world, I wouldn't have to make `U` concrete any sooner than that.) Of course, as you rightly point out, I can't do this because variable, like `char`, can't have free generics. – fmg Oct 17 '18 at 22:46
  • You can use a type assertion as you have, but you could let `U` be `unkown` and when you call `parse` you could specify the concrete `U`. I am not sure how that would work with the composition part of your API though. – Titian Cernicova-Dragomir Oct 17 '18 at 22:50
  • I didn't know about `unknown`. Looking into it... – fmg Oct 17 '18 at 22:52
  • Stricter version of `any`. Shameless plug: https://stackoverflow.com/questions/51439843/typescript-3-0-unknown-vs-any/51439876#51439876 – Titian Cernicova-Dragomir Oct 17 '18 at 22:53

0 Answers0