Using GADT variants in a polymorphic, compound type as I would normal algebraic variant types?












1















Let's say I have this simple variant-type:



type flag = {
name: string;
payload: string option;
}

type word =
| Arg of string
| Flag of flag

let args = [|
Arg "hello";
Flag {name = "foo"; payload = Some "world"};
|]


If, however, I want to add GADT-constraints to that word type,



type _ word =
| Arg : string -> string word
| Flag : flag -> flag word


… the compiler can no longer infer a general type for the members of args:



Line 12, 3:
This expression has type flag word
but an expression was expected of type string word
Type flag is not compatible with type string


Is this simply a limitation of GADTs?










share|improve this question

























  • you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

    – Stas
    Nov 22 '18 at 6:55
















1















Let's say I have this simple variant-type:



type flag = {
name: string;
payload: string option;
}

type word =
| Arg of string
| Flag of flag

let args = [|
Arg "hello";
Flag {name = "foo"; payload = Some "world"};
|]


If, however, I want to add GADT-constraints to that word type,



type _ word =
| Arg : string -> string word
| Flag : flag -> flag word


… the compiler can no longer infer a general type for the members of args:



Line 12, 3:
This expression has type flag word
but an expression was expected of type string word
Type flag is not compatible with type string


Is this simply a limitation of GADTs?










share|improve this question

























  • you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

    – Stas
    Nov 22 '18 at 6:55














1












1








1








Let's say I have this simple variant-type:



type flag = {
name: string;
payload: string option;
}

type word =
| Arg of string
| Flag of flag

let args = [|
Arg "hello";
Flag {name = "foo"; payload = Some "world"};
|]


If, however, I want to add GADT-constraints to that word type,



type _ word =
| Arg : string -> string word
| Flag : flag -> flag word


… the compiler can no longer infer a general type for the members of args:



Line 12, 3:
This expression has type flag word
but an expression was expected of type string word
Type flag is not compatible with type string


Is this simply a limitation of GADTs?










share|improve this question
















Let's say I have this simple variant-type:



type flag = {
name: string;
payload: string option;
}

type word =
| Arg of string
| Flag of flag

let args = [|
Arg "hello";
Flag {name = "foo"; payload = Some "world"};
|]


If, however, I want to add GADT-constraints to that word type,



type _ word =
| Arg : string -> string word
| Flag : flag -> flag word


… the compiler can no longer infer a general type for the members of args:



Line 12, 3:
This expression has type flag word
but an expression was expected of type string word
Type flag is not compatible with type string


Is this simply a limitation of GADTs?







polymorphism ocaml algebraic-data-types gadt






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 '18 at 0:24









glennsl

9,914102848




9,914102848










asked Nov 20 '18 at 23:26









ELLIOTTCABLEELLIOTTCABLE

10.4k84261




10.4k84261













  • you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

    – Stas
    Nov 22 '18 at 6:55



















  • you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

    – Stas
    Nov 22 '18 at 6:55

















you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

– Stas
Nov 22 '18 at 6:55





you just don't need GADTs in your case. It is not a limitation of GADTs, but a feature :) Look at that experiment (building type safe evaluator) stackoverflow.com/questions/30429552/…

– Stas
Nov 22 '18 at 6:55












3 Answers
3






active

oldest

votes


















2














I'm certainly no expert on GADTs, but this seems more like a limitation of ordinary type variables than of GADTs. flag word and string word are distinct types that won't fit into the same array no matter what.



I assume what you want is an existentially quantified type variable. If so the existential type variable should only occur on the left side of the arrow. This seems to work:



type word =
| Arg : string -> word
| Flag : flag -> word


Or in the words of the manual:




Variables are made existential when they appear inside a constructor’s argument, but not in its return type.






Edit: Nope, as @octachron points out in a comment, this is not how existential types work. Or rather existential type variables, of which there are none in my example. The cause is the same, but I'm not entirely clear on what property of a GADT is desired here and what therefore the solution would be.






share|improve this answer


























  • Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

    – octachron
    Nov 21 '18 at 8:45











  • @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

    – glennsl
    Nov 21 '18 at 10:00






  • 1





    See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

    – ELLIOTTCABLE
    Nov 21 '18 at 17:25



















1














It is not so much a limitation of GADTs than one of their desired properties. When you write



type _ word =
| Arg : string -> string word
| Flag : flag -> flag word


you are asking the typechecker to make the type of Arg _ and Flag _ different and incompatible.



To motivate this behavior, a better example might be a list with a static length:



 type zero = Zero
type 'a succ = Succ
type ('elt,'size) nlist =
| : ('elt, zero) nlist
| (::): 'elt * ('elt, 'size) nlist -> ('elt, 'size succ) nlist


With this definition a value of type ('n, 's) nlist carries an encoding of its length inside this type. This makes it possible to write a total hd function



  let hd (a::q) = a


Since our exotic list type carries its length in its type, the typechecker can express the fact that hd only accepts list with one or more argument (i.e. the type of hd is
('elt,_ succ) nlist -> 'elt). Thus the function hd always returns (when its type checks).



But this also means that the type checker must now enforce than the function hd always works. In other words, an array mixing length of different size



  [| ; [1]; [1;2] |]


cannot be allowed to type-check because it contains element for which the hd function is not well-defined and the type checker guaranteed to us earlier than hd will always return a successful value.



Returning to your example, with your type definition, you have made it possible to discriminate between Flag _ and Int _. Thus I can write the following total function



 let empty (Arg _) = ""
let map_empty = Array.map empty


and expect than map_empty works on all well-typed arrays. But I cannot possibly apply this function to your mixed array



 let args = [|
Arg "hello";
Flag {name = "foo"; payload = Some "world"};
|]


In other words, this array cannot be well-typed.






share|improve this answer

































    0














    The usual way to handle this situation with GADTs is to introduce existential wrappers (here, any_word):



    type flag = {
    name : string;
    payload : string option;
    }

    type _ word =
    | Arg : string -> string word
    | Flag : flag -> flag word

    type any_word = Word : _ word -> any_word [@@unboxed]

    let args = [|
    Word (Arg "hello");
    Word (Flag {name = "foo"; payload = Some "world"});
    |]


    args contains words of unknown type index, that index being hidden inside the any_word wrapper. In contrast,



    let flag_args = [|
    Flag {name = "zonk"; payload = None};
    Flag {name = "splines"; payload = Some "reticulate"};
    |]


    flag_args contains flag words specifically. string words are excluded by the type system, which is one of the attractions of GADTs. (If you don't need this, GADTs are not likely to be helpful.)






    share|improve this answer























      Your Answer






      StackExchange.ifUsing("editor", function () {
      StackExchange.using("externalEditor", function () {
      StackExchange.using("snippets", function () {
      StackExchange.snippets.init();
      });
      });
      }, "code-snippets");

      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "1"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53403143%2fusing-gadt-variants-in-a-polymorphic-compound-type-as-i-would-normal-algebraic%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      3 Answers
      3






      active

      oldest

      votes








      3 Answers
      3






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      2














      I'm certainly no expert on GADTs, but this seems more like a limitation of ordinary type variables than of GADTs. flag word and string word are distinct types that won't fit into the same array no matter what.



      I assume what you want is an existentially quantified type variable. If so the existential type variable should only occur on the left side of the arrow. This seems to work:



      type word =
      | Arg : string -> word
      | Flag : flag -> word


      Or in the words of the manual:




      Variables are made existential when they appear inside a constructor’s argument, but not in its return type.






      Edit: Nope, as @octachron points out in a comment, this is not how existential types work. Or rather existential type variables, of which there are none in my example. The cause is the same, but I'm not entirely clear on what property of a GADT is desired here and what therefore the solution would be.






      share|improve this answer


























      • Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

        – octachron
        Nov 21 '18 at 8:45











      • @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

        – glennsl
        Nov 21 '18 at 10:00






      • 1





        See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

        – ELLIOTTCABLE
        Nov 21 '18 at 17:25
















      2














      I'm certainly no expert on GADTs, but this seems more like a limitation of ordinary type variables than of GADTs. flag word and string word are distinct types that won't fit into the same array no matter what.



      I assume what you want is an existentially quantified type variable. If so the existential type variable should only occur on the left side of the arrow. This seems to work:



      type word =
      | Arg : string -> word
      | Flag : flag -> word


      Or in the words of the manual:




      Variables are made existential when they appear inside a constructor’s argument, but not in its return type.






      Edit: Nope, as @octachron points out in a comment, this is not how existential types work. Or rather existential type variables, of which there are none in my example. The cause is the same, but I'm not entirely clear on what property of a GADT is desired here and what therefore the solution would be.






      share|improve this answer


























      • Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

        – octachron
        Nov 21 '18 at 8:45











      • @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

        – glennsl
        Nov 21 '18 at 10:00






      • 1





        See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

        – ELLIOTTCABLE
        Nov 21 '18 at 17:25














      2












      2








      2







      I'm certainly no expert on GADTs, but this seems more like a limitation of ordinary type variables than of GADTs. flag word and string word are distinct types that won't fit into the same array no matter what.



      I assume what you want is an existentially quantified type variable. If so the existential type variable should only occur on the left side of the arrow. This seems to work:



      type word =
      | Arg : string -> word
      | Flag : flag -> word


      Or in the words of the manual:




      Variables are made existential when they appear inside a constructor’s argument, but not in its return type.






      Edit: Nope, as @octachron points out in a comment, this is not how existential types work. Or rather existential type variables, of which there are none in my example. The cause is the same, but I'm not entirely clear on what property of a GADT is desired here and what therefore the solution would be.






      share|improve this answer















      I'm certainly no expert on GADTs, but this seems more like a limitation of ordinary type variables than of GADTs. flag word and string word are distinct types that won't fit into the same array no matter what.



      I assume what you want is an existentially quantified type variable. If so the existential type variable should only occur on the left side of the arrow. This seems to work:



      type word =
      | Arg : string -> word
      | Flag : flag -> word


      Or in the words of the manual:




      Variables are made existential when they appear inside a constructor’s argument, but not in its return type.






      Edit: Nope, as @octachron points out in a comment, this is not how existential types work. Or rather existential type variables, of which there are none in my example. The cause is the same, but I'm not entirely clear on what property of a GADT is desired here and what therefore the solution would be.







      share|improve this answer














      share|improve this answer



      share|improve this answer








      edited Nov 21 '18 at 10:03

























      answered Nov 21 '18 at 0:17









      glennslglennsl

      9,914102848




      9,914102848













      • Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

        – octachron
        Nov 21 '18 at 8:45











      • @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

        – glennsl
        Nov 21 '18 at 10:00






      • 1





        See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

        – ELLIOTTCABLE
        Nov 21 '18 at 17:25



















      • Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

        – octachron
        Nov 21 '18 at 8:45











      • @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

        – glennsl
        Nov 21 '18 at 10:00






      • 1





        See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

        – ELLIOTTCABLE
        Nov 21 '18 at 17:25

















      Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

      – octachron
      Nov 21 '18 at 8:45





      Note that your type word is equivalent to the classical variant type word = Arg of string | Flag of flag since there is no type variables in the constructor definitions. An existential quantification would be more akin to type dyn_word = Dyn: 'a word -> dyn_word where 'a is existentially quantified .

      – octachron
      Nov 21 '18 at 8:45













      @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

      – glennsl
      Nov 21 '18 at 10:00





      @octachron Right, lol, of course :) It's not entirely clear to me what purpose OP had in mind for using a GADT then.

      – glennsl
      Nov 21 '18 at 10:00




      1




      1





      See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

      – ELLIOTTCABLE
      Nov 21 '18 at 17:25





      See the post — I started with a classical variant, and tried to add a GADT constraint to it! As for the goal, it's simply to experiment with GADTs, start to get a feel for when I would use them in practice, see what restrictions they add. No particular goal. 😱

      – ELLIOTTCABLE
      Nov 21 '18 at 17:25













      1














      It is not so much a limitation of GADTs than one of their desired properties. When you write



      type _ word =
      | Arg : string -> string word
      | Flag : flag -> flag word


      you are asking the typechecker to make the type of Arg _ and Flag _ different and incompatible.



      To motivate this behavior, a better example might be a list with a static length:



       type zero = Zero
      type 'a succ = Succ
      type ('elt,'size) nlist =
      | : ('elt, zero) nlist
      | (::): 'elt * ('elt, 'size) nlist -> ('elt, 'size succ) nlist


      With this definition a value of type ('n, 's) nlist carries an encoding of its length inside this type. This makes it possible to write a total hd function



        let hd (a::q) = a


      Since our exotic list type carries its length in its type, the typechecker can express the fact that hd only accepts list with one or more argument (i.e. the type of hd is
      ('elt,_ succ) nlist -> 'elt). Thus the function hd always returns (when its type checks).



      But this also means that the type checker must now enforce than the function hd always works. In other words, an array mixing length of different size



        [| ; [1]; [1;2] |]


      cannot be allowed to type-check because it contains element for which the hd function is not well-defined and the type checker guaranteed to us earlier than hd will always return a successful value.



      Returning to your example, with your type definition, you have made it possible to discriminate between Flag _ and Int _. Thus I can write the following total function



       let empty (Arg _) = ""
      let map_empty = Array.map empty


      and expect than map_empty works on all well-typed arrays. But I cannot possibly apply this function to your mixed array



       let args = [|
      Arg "hello";
      Flag {name = "foo"; payload = Some "world"};
      |]


      In other words, this array cannot be well-typed.






      share|improve this answer






























        1














        It is not so much a limitation of GADTs than one of their desired properties. When you write



        type _ word =
        | Arg : string -> string word
        | Flag : flag -> flag word


        you are asking the typechecker to make the type of Arg _ and Flag _ different and incompatible.



        To motivate this behavior, a better example might be a list with a static length:



         type zero = Zero
        type 'a succ = Succ
        type ('elt,'size) nlist =
        | : ('elt, zero) nlist
        | (::): 'elt * ('elt, 'size) nlist -> ('elt, 'size succ) nlist


        With this definition a value of type ('n, 's) nlist carries an encoding of its length inside this type. This makes it possible to write a total hd function



          let hd (a::q) = a


        Since our exotic list type carries its length in its type, the typechecker can express the fact that hd only accepts list with one or more argument (i.e. the type of hd is
        ('elt,_ succ) nlist -> 'elt). Thus the function hd always returns (when its type checks).



        But this also means that the type checker must now enforce than the function hd always works. In other words, an array mixing length of different size



          [| ; [1]; [1;2] |]


        cannot be allowed to type-check because it contains element for which the hd function is not well-defined and the type checker guaranteed to us earlier than hd will always return a successful value.



        Returning to your example, with your type definition, you have made it possible to discriminate between Flag _ and Int _. Thus I can write the following total function



         let empty (Arg _) = ""
        let map_empty = Array.map empty


        and expect than map_empty works on all well-typed arrays. But I cannot possibly apply this function to your mixed array



         let args = [|
        Arg "hello";
        Flag {name = "foo"; payload = Some "world"};
        |]


        In other words, this array cannot be well-typed.






        share|improve this answer




























          1












          1








          1







          It is not so much a limitation of GADTs than one of their desired properties. When you write



          type _ word =
          | Arg : string -> string word
          | Flag : flag -> flag word


          you are asking the typechecker to make the type of Arg _ and Flag _ different and incompatible.



          To motivate this behavior, a better example might be a list with a static length:



           type zero = Zero
          type 'a succ = Succ
          type ('elt,'size) nlist =
          | : ('elt, zero) nlist
          | (::): 'elt * ('elt, 'size) nlist -> ('elt, 'size succ) nlist


          With this definition a value of type ('n, 's) nlist carries an encoding of its length inside this type. This makes it possible to write a total hd function



            let hd (a::q) = a


          Since our exotic list type carries its length in its type, the typechecker can express the fact that hd only accepts list with one or more argument (i.e. the type of hd is
          ('elt,_ succ) nlist -> 'elt). Thus the function hd always returns (when its type checks).



          But this also means that the type checker must now enforce than the function hd always works. In other words, an array mixing length of different size



            [| ; [1]; [1;2] |]


          cannot be allowed to type-check because it contains element for which the hd function is not well-defined and the type checker guaranteed to us earlier than hd will always return a successful value.



          Returning to your example, with your type definition, you have made it possible to discriminate between Flag _ and Int _. Thus I can write the following total function



           let empty (Arg _) = ""
          let map_empty = Array.map empty


          and expect than map_empty works on all well-typed arrays. But I cannot possibly apply this function to your mixed array



           let args = [|
          Arg "hello";
          Flag {name = "foo"; payload = Some "world"};
          |]


          In other words, this array cannot be well-typed.






          share|improve this answer















          It is not so much a limitation of GADTs than one of their desired properties. When you write



          type _ word =
          | Arg : string -> string word
          | Flag : flag -> flag word


          you are asking the typechecker to make the type of Arg _ and Flag _ different and incompatible.



          To motivate this behavior, a better example might be a list with a static length:



           type zero = Zero
          type 'a succ = Succ
          type ('elt,'size) nlist =
          | : ('elt, zero) nlist
          | (::): 'elt * ('elt, 'size) nlist -> ('elt, 'size succ) nlist


          With this definition a value of type ('n, 's) nlist carries an encoding of its length inside this type. This makes it possible to write a total hd function



            let hd (a::q) = a


          Since our exotic list type carries its length in its type, the typechecker can express the fact that hd only accepts list with one or more argument (i.e. the type of hd is
          ('elt,_ succ) nlist -> 'elt). Thus the function hd always returns (when its type checks).



          But this also means that the type checker must now enforce than the function hd always works. In other words, an array mixing length of different size



            [| ; [1]; [1;2] |]


          cannot be allowed to type-check because it contains element for which the hd function is not well-defined and the type checker guaranteed to us earlier than hd will always return a successful value.



          Returning to your example, with your type definition, you have made it possible to discriminate between Flag _ and Int _. Thus I can write the following total function



           let empty (Arg _) = ""
          let map_empty = Array.map empty


          and expect than map_empty works on all well-typed arrays. But I cannot possibly apply this function to your mixed array



           let args = [|
          Arg "hello";
          Flag {name = "foo"; payload = Some "world"};
          |]


          In other words, this array cannot be well-typed.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 22 '18 at 12:29

























          answered Nov 22 '18 at 8:16









          octachronoctachron

          4,4281513




          4,4281513























              0














              The usual way to handle this situation with GADTs is to introduce existential wrappers (here, any_word):



              type flag = {
              name : string;
              payload : string option;
              }

              type _ word =
              | Arg : string -> string word
              | Flag : flag -> flag word

              type any_word = Word : _ word -> any_word [@@unboxed]

              let args = [|
              Word (Arg "hello");
              Word (Flag {name = "foo"; payload = Some "world"});
              |]


              args contains words of unknown type index, that index being hidden inside the any_word wrapper. In contrast,



              let flag_args = [|
              Flag {name = "zonk"; payload = None};
              Flag {name = "splines"; payload = Some "reticulate"};
              |]


              flag_args contains flag words specifically. string words are excluded by the type system, which is one of the attractions of GADTs. (If you don't need this, GADTs are not likely to be helpful.)






              share|improve this answer




























                0














                The usual way to handle this situation with GADTs is to introduce existential wrappers (here, any_word):



                type flag = {
                name : string;
                payload : string option;
                }

                type _ word =
                | Arg : string -> string word
                | Flag : flag -> flag word

                type any_word = Word : _ word -> any_word [@@unboxed]

                let args = [|
                Word (Arg "hello");
                Word (Flag {name = "foo"; payload = Some "world"});
                |]


                args contains words of unknown type index, that index being hidden inside the any_word wrapper. In contrast,



                let flag_args = [|
                Flag {name = "zonk"; payload = None};
                Flag {name = "splines"; payload = Some "reticulate"};
                |]


                flag_args contains flag words specifically. string words are excluded by the type system, which is one of the attractions of GADTs. (If you don't need this, GADTs are not likely to be helpful.)






                share|improve this answer


























                  0












                  0








                  0







                  The usual way to handle this situation with GADTs is to introduce existential wrappers (here, any_word):



                  type flag = {
                  name : string;
                  payload : string option;
                  }

                  type _ word =
                  | Arg : string -> string word
                  | Flag : flag -> flag word

                  type any_word = Word : _ word -> any_word [@@unboxed]

                  let args = [|
                  Word (Arg "hello");
                  Word (Flag {name = "foo"; payload = Some "world"});
                  |]


                  args contains words of unknown type index, that index being hidden inside the any_word wrapper. In contrast,



                  let flag_args = [|
                  Flag {name = "zonk"; payload = None};
                  Flag {name = "splines"; payload = Some "reticulate"};
                  |]


                  flag_args contains flag words specifically. string words are excluded by the type system, which is one of the attractions of GADTs. (If you don't need this, GADTs are not likely to be helpful.)






                  share|improve this answer













                  The usual way to handle this situation with GADTs is to introduce existential wrappers (here, any_word):



                  type flag = {
                  name : string;
                  payload : string option;
                  }

                  type _ word =
                  | Arg : string -> string word
                  | Flag : flag -> flag word

                  type any_word = Word : _ word -> any_word [@@unboxed]

                  let args = [|
                  Word (Arg "hello");
                  Word (Flag {name = "foo"; payload = Some "world"});
                  |]


                  args contains words of unknown type index, that index being hidden inside the any_word wrapper. In contrast,



                  let flag_args = [|
                  Flag {name = "zonk"; payload = None};
                  Flag {name = "splines"; payload = Some "reticulate"};
                  |]


                  flag_args contains flag words specifically. string words are excluded by the type system, which is one of the attractions of GADTs. (If you don't need this, GADTs are not likely to be helpful.)







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 23 '18 at 7:02









                  gsggsg

                  7,82911118




                  7,82911118






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53403143%2fusing-gadt-variants-in-a-polymorphic-compound-type-as-i-would-normal-algebraic%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      "Incorrect syntax near the keyword 'ON'. (on update cascade, on delete cascade,)

                      Alcedinidae

                      Origin of the phrase “under your belt”?