Generics, type inference and getting stuck

Hi folks, me again having great fun tinkering with crystal …

I sketched a parser framework in the spirit of rusty nom.

The resulting code looks like this:

    children = optional(
        enclosed(
          enclosed(whitespace0, tag("{"), whitespace0),
          delimited0(
            whitespace1,
            either(
              boolean,
              alphanumeric
            )
          ),
          enclosed(whitespace0, tag("}"), whitespace0),
        )
      ).call(somebytes)

The central building block is a parser, which is a Proc which takes Bytes and eventually returns a Result holding the parsed value and the remaining not consumed Bytes: Proc(Bytes, Result(T)?). The parsed return value is generic so therefore the whole parser Proc is generic.

a sample of a working parser factory method:

# takes two parsers which are applied one after another
# both must succeed
# the result of the first one is dropped while the result
# of the second one is returned
def prefixed(prefix : Proc(Bytes, Result(A)?), parser : Proc(Bytes, Result(B)?)) forall A, B
  Proc(Bytes, Result(B)?).new do |input|
    if prefix_result = prefix.call(input)
      parser.call(prefix_result.rest)
    end
  end
end

but now I 've got stuck on the following construct:

# takes a variable number of parsers
# returns the result of the first one that succeeds
def either(*variants : Proc(Bytes, Result(*T)?)) forall T
  Proc(Bytes,Result(*T)?).new do |input|
    match = nil
    variants.each do |variant|
      if match = variant.call(input)
        break
      end
    end

    match.try do |result|
      Result(*T).new(result.value, result.rest)
    end
  end
end

This construct works when I call it with parsers of the same result type.
But if I try with parsers of different types (like in the sample above either(boolean, alphanumeric): the boolean parser is of type Bool whereas the alphanumeric parser is of type String), the compiler refuses to find a matching overload…
I took my inspiration from here Generics - Crystal … but I guess my current approach is too naive …

The basic idea is that I have multiple generic typed Procs as input and have to return a generic Proc where the generic type is the union of all generics from the input. So for example:

Proc(Bytes,Result(Bool)?) , Proc(Bytes,Result(String)?) => Proc(Bytes,Result(Bool|String)?)

Is this possible at all?!

1 Like

Expressing this in terms of free variables would be difficult, but you could try this: Carcin

1 Like

Hi
I don’t fully understand the intent you are trying to do, but the JSON Any implementation seems to be helpful?

https://crystal-lang.org/api/JSON/Any.html

:star_struck: Works! … and it took you just minutes … Thank you!

I wasnt aware of that ->() proc literal notation. And its also not clear yet, why/how the constructor call to the generic Result type works without generic argument but I will learn …

https://crystal-lang.org/reference/1.13/syntax_and_semantics/generics.html#generic-class-methods