Similar record types in a list/array in purescript












2














Is there any way to do something like



first = {x:0}
second = {x:1,y:1}
both = [first, second]


such that both is inferred as {x::Int | r} or something like that?



I've tried a few things:



[{x:3}] :: Array(forall r. {x::Int|r})    -- nope

test = Nil :: List(forall r. {x::Int|r})
{x:1} : test -- nope

type X r = {x::Int | r}
test = Nil :: List(X) -- nope
test = Nil :: List(X())
{x:1} : test
{x:1, y:1} : test -- nope


Everything I can think of seems to tell me that combining records like this into a collection is not supported. Kind of like, a function can be polymorphic but a list cannot. Is that the correct interpretation? It reminds me a bit of the F# "value restriction" problem, though I thought that was just because of CLR restrictions whereas JS should not have that issue. But maybe it's unrelated.



Is there any way to declare the list/array to support this?










share|improve this question





























    2














    Is there any way to do something like



    first = {x:0}
    second = {x:1,y:1}
    both = [first, second]


    such that both is inferred as {x::Int | r} or something like that?



    I've tried a few things:



    [{x:3}] :: Array(forall r. {x::Int|r})    -- nope

    test = Nil :: List(forall r. {x::Int|r})
    {x:1} : test -- nope

    type X r = {x::Int | r}
    test = Nil :: List(X) -- nope
    test = Nil :: List(X())
    {x:1} : test
    {x:1, y:1} : test -- nope


    Everything I can think of seems to tell me that combining records like this into a collection is not supported. Kind of like, a function can be polymorphic but a list cannot. Is that the correct interpretation? It reminds me a bit of the F# "value restriction" problem, though I thought that was just because of CLR restrictions whereas JS should not have that issue. But maybe it's unrelated.



    Is there any way to declare the list/array to support this?










    share|improve this question



























      2












      2








      2


      0





      Is there any way to do something like



      first = {x:0}
      second = {x:1,y:1}
      both = [first, second]


      such that both is inferred as {x::Int | r} or something like that?



      I've tried a few things:



      [{x:3}] :: Array(forall r. {x::Int|r})    -- nope

      test = Nil :: List(forall r. {x::Int|r})
      {x:1} : test -- nope

      type X r = {x::Int | r}
      test = Nil :: List(X) -- nope
      test = Nil :: List(X())
      {x:1} : test
      {x:1, y:1} : test -- nope


      Everything I can think of seems to tell me that combining records like this into a collection is not supported. Kind of like, a function can be polymorphic but a list cannot. Is that the correct interpretation? It reminds me a bit of the F# "value restriction" problem, though I thought that was just because of CLR restrictions whereas JS should not have that issue. But maybe it's unrelated.



      Is there any way to declare the list/array to support this?










      share|improve this question















      Is there any way to do something like



      first = {x:0}
      second = {x:1,y:1}
      both = [first, second]


      such that both is inferred as {x::Int | r} or something like that?



      I've tried a few things:



      [{x:3}] :: Array(forall r. {x::Int|r})    -- nope

      test = Nil :: List(forall r. {x::Int|r})
      {x:1} : test -- nope

      type X r = {x::Int | r}
      test = Nil :: List(X) -- nope
      test = Nil :: List(X())
      {x:1} : test
      {x:1, y:1} : test -- nope


      Everything I can think of seems to tell me that combining records like this into a collection is not supported. Kind of like, a function can be polymorphic but a list cannot. Is that the correct interpretation? It reminds me a bit of the F# "value restriction" problem, though I thought that was just because of CLR restrictions whereas JS should not have that issue. But maybe it's unrelated.



      Is there any way to declare the list/array to support this?







      purescript






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 12 '18 at 22:56







      Dax Fohl

















      asked Nov 12 '18 at 21:11









      Dax FohlDax Fohl

      7,45842773




      7,45842773
























          2 Answers
          2






          active

          oldest

          votes


















          3














          What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)



          One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:



          type RecordRep = Unit -> Int

          toRecordRep :: forall r. { x :: Int | r } -> RecordRep
          toRecordRep {x} _ = x

          -- Construct the array using `toRecordRep`
          test :: Array RecordRep
          test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]

          -- Later use the operation
          allTheXs :: Array Int
          allTheXs = test <#> r -> r unit


          If you have multiple such operations, you can always make a record of them:



          type RecordRep = 
          { getX :: Unit -> Int
          , show :: Unit -> String
          , toJavaScript :: Unit -> Foreign.Object
          }

          toRecordRep r =
          { getX: const r.x
          , show: const $ show r.x
          , toJavaScript: const $ unsafeCoerce r
          }


          (note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)



          But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)



           newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. {x::Int|r} -> RecordRep
          toRecordRep r f = f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          allTheXs = test <#> (RecordRep r) -> r _.x


          The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.



          Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.



          Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:



          newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
          toRecordRep r = RecordRep f -> f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          showAll = test <#> (RecordRep r) -> r show


          Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.



          And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.



          Incidentally, this is how Haskell language support for this works as well.






          share|improve this answer





















          • That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
            – Dax Fohl
            Nov 13 '18 at 18:22










          • Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
            – Fyodor Soikin
            Nov 13 '18 at 19:48










          • Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
            – Dax Fohl
            Nov 13 '18 at 20:00



















          1














          Folloing @FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
          Finally we will be able to build an Array of records which will be "isomorphic" to:



          exists tail. Array { x :: Int | tail }


          Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.



          newtype Exists f = Exists (forall a. f (a :: #Type))


          We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:



          module Main where

          import Prelude

          import Unsafe.Coerce (unsafeCoerce)
          import Data.Newtype (class Newtype, unwrap)

          newtype Exists f = Exists (forall a. f (a :: #Type))

          mkExists :: forall f a. f a -> Exists f
          mkExists r = Exists (unsafeCoerce r :: forall a. f a)

          runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
          runExists g (Exists f) = g f


          Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:



          newtype R t = R { x :: Int | t }
          derive instance newtypeRec :: Newtype (R t) _


          Now we can build an Array using mkExists:



          arr :: Array (Exists R)
          arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]


          and process values using runExists:



          x :: Array [ Int ]
          x = map (runExists (unwrap >>> _.x)) arr





          share|improve this answer























          • The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
            – Fyodor Soikin
            Nov 13 '18 at 23:24










          • I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
            – paluh
            Nov 13 '18 at 23:47












          • If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
            – Fyodor Soikin
            Nov 14 '18 at 0:17












          • I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
            – paluh
            Nov 14 '18 at 1:11











          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%2f53270182%2fsimilar-record-types-in-a-list-array-in-purescript%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          3














          What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)



          One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:



          type RecordRep = Unit -> Int

          toRecordRep :: forall r. { x :: Int | r } -> RecordRep
          toRecordRep {x} _ = x

          -- Construct the array using `toRecordRep`
          test :: Array RecordRep
          test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]

          -- Later use the operation
          allTheXs :: Array Int
          allTheXs = test <#> r -> r unit


          If you have multiple such operations, you can always make a record of them:



          type RecordRep = 
          { getX :: Unit -> Int
          , show :: Unit -> String
          , toJavaScript :: Unit -> Foreign.Object
          }

          toRecordRep r =
          { getX: const r.x
          , show: const $ show r.x
          , toJavaScript: const $ unsafeCoerce r
          }


          (note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)



          But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)



           newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. {x::Int|r} -> RecordRep
          toRecordRep r f = f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          allTheXs = test <#> (RecordRep r) -> r _.x


          The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.



          Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.



          Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:



          newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
          toRecordRep r = RecordRep f -> f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          showAll = test <#> (RecordRep r) -> r show


          Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.



          And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.



          Incidentally, this is how Haskell language support for this works as well.






          share|improve this answer





















          • That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
            – Dax Fohl
            Nov 13 '18 at 18:22










          • Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
            – Fyodor Soikin
            Nov 13 '18 at 19:48










          • Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
            – Dax Fohl
            Nov 13 '18 at 20:00
















          3














          What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)



          One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:



          type RecordRep = Unit -> Int

          toRecordRep :: forall r. { x :: Int | r } -> RecordRep
          toRecordRep {x} _ = x

          -- Construct the array using `toRecordRep`
          test :: Array RecordRep
          test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]

          -- Later use the operation
          allTheXs :: Array Int
          allTheXs = test <#> r -> r unit


          If you have multiple such operations, you can always make a record of them:



          type RecordRep = 
          { getX :: Unit -> Int
          , show :: Unit -> String
          , toJavaScript :: Unit -> Foreign.Object
          }

          toRecordRep r =
          { getX: const r.x
          , show: const $ show r.x
          , toJavaScript: const $ unsafeCoerce r
          }


          (note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)



          But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)



           newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. {x::Int|r} -> RecordRep
          toRecordRep r f = f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          allTheXs = test <#> (RecordRep r) -> r _.x


          The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.



          Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.



          Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:



          newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
          toRecordRep r = RecordRep f -> f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          showAll = test <#> (RecordRep r) -> r show


          Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.



          And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.



          Incidentally, this is how Haskell language support for this works as well.






          share|improve this answer





















          • That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
            – Dax Fohl
            Nov 13 '18 at 18:22










          • Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
            – Fyodor Soikin
            Nov 13 '18 at 19:48










          • Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
            – Dax Fohl
            Nov 13 '18 at 20:00














          3












          3








          3






          What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)



          One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:



          type RecordRep = Unit -> Int

          toRecordRep :: forall r. { x :: Int | r } -> RecordRep
          toRecordRep {x} _ = x

          -- Construct the array using `toRecordRep`
          test :: Array RecordRep
          test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]

          -- Later use the operation
          allTheXs :: Array Int
          allTheXs = test <#> r -> r unit


          If you have multiple such operations, you can always make a record of them:



          type RecordRep = 
          { getX :: Unit -> Int
          , show :: Unit -> String
          , toJavaScript :: Unit -> Foreign.Object
          }

          toRecordRep r =
          { getX: const r.x
          , show: const $ show r.x
          , toJavaScript: const $ unsafeCoerce r
          }


          (note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)



          But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)



           newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. {x::Int|r} -> RecordRep
          toRecordRep r f = f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          allTheXs = test <#> (RecordRep r) -> r _.x


          The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.



          Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.



          Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:



          newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
          toRecordRep r = RecordRep f -> f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          showAll = test <#> (RecordRep r) -> r show


          Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.



          And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.



          Incidentally, this is how Haskell language support for this works as well.






          share|improve this answer












          What you're looking for is "existential types", and PureScript just doesn't support those at the syntax level the way Haskell does. But you can roll your own :-)



          One way to go is "data abstraction" - i.e. encode the data in terms of operations you'll want to perform on it. For example, let's say you'll want to get the value of x out of them at some point. In that case, make an array of these:



          type RecordRep = Unit -> Int

          toRecordRep :: forall r. { x :: Int | r } -> RecordRep
          toRecordRep {x} _ = x

          -- Construct the array using `toRecordRep`
          test :: Array RecordRep
          test = [ toRecordRep {x:1}, toRecordRep {x:1, y:1} ]

          -- Later use the operation
          allTheXs :: Array Int
          allTheXs = test <#> r -> r unit


          If you have multiple such operations, you can always make a record of them:



          type RecordRep = 
          { getX :: Unit -> Int
          , show :: Unit -> String
          , toJavaScript :: Unit -> Foreign.Object
          }

          toRecordRep r =
          { getX: const r.x
          , show: const $ show r.x
          , toJavaScript: const $ unsafeCoerce r
          }


          (note the Unit arguments in every function - they're there for the laziness, assuming each operation could be expensive)



          But if you really need the type machinery, you can do what I call "poor man's existential type". If you look closely, existential types are nothing more than "deferred" type checks - deferred to the point where you'll need to see the type. And what's a mechanism to defer something in an ML language? That's right - a function! :-)



           newtype RecordRep = RecordRep (forall a. (forall r. {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. {x::Int|r} -> RecordRep
          toRecordRep r f = f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          allTheXs = test <#> (RecordRep r) -> r _.x


          The way this works is that RecordRep wraps a function, which takes another function, which is polymorphic in r - that is, if you're looking at a RecordRep, you must be prepared to give it a function that can work with any r. toRecordRep wraps the record in such a way that its precise type is not visible on the outside, but it will be used to instantiate the generic function, which you will eventually provide. In my example such function is _.x.



          Note, however, that herein lies the problem: the row r is literally not known when you get to work with an element of the array, so you can't do anything with it. Like, at all. All you can do is get the x field, because its existence is hardcoded in the signatures, but besides the x - you just don't know. And that's by design: if you want to put anything into the array, you must be prepared to get anything out of it.



          Now, if you do want to do something with the values after all, you'll have to explain that by constraining r, for example:



          newtype RecordRep = RecordRep (forall a. (forall r. Show {x::Int|r} => {x::Int|r} -> a) -> a)

          toRecordRep :: forall r. Show {x::Int|r} => {x::Int|r} -> RecordRep
          toRecordRep r = RecordRep f -> f r

          test :: Array RecordRep
          test = [toRecordRep {x:1}, toRecordRep {x:1, y:1}]

          showAll = test <#> (RecordRep r) -> r show


          Passing the show function like this works, because we have constrained the row r in such a way that Show {x::Int|r} must exist, and therefore, applying show to {x::Int|r} must work. Repeat for your own type classes as needed.



          And here's the interesting part: since type classes are implemented as dictionaries of functions, the two options described above are actually equivalent - in both cases you end up passing around a dictionary of functions, only in the first case it's explicit, but in the second case the compiler does it for you.



          Incidentally, this is how Haskell language support for this works as well.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 13 '18 at 16:39









          Fyodor SoikinFyodor Soikin

          42.2k56497




          42.2k56497












          • That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
            – Dax Fohl
            Nov 13 '18 at 18:22










          • Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
            – Fyodor Soikin
            Nov 13 '18 at 19:48










          • Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
            – Dax Fohl
            Nov 13 '18 at 20:00


















          • That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
            – Dax Fohl
            Nov 13 '18 at 18:22










          • Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
            – Fyodor Soikin
            Nov 13 '18 at 19:48










          • Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
            – Dax Fohl
            Nov 13 '18 at 20:00
















          That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
          – Dax Fohl
          Nov 13 '18 at 18:22




          That's interesting, you hit on exactly what I'm investigating. If classes and sets of functions are equivalent, is there a need for classes at all? i.e. instead of defining/implementing class Show, couldn't there be functions that takes objects of various types and append .show() function to them? Of course then you'd need the ability to attach new fields to a record non-reflectively, which ps doesn't have.
          – Dax Fohl
          Nov 13 '18 at 18:22












          Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
          – Fyodor Soikin
          Nov 13 '18 at 19:48




          Classes differ from explicit function dictionaries in that the compiler takes care of passing them around for you. Without type classes most functions would have extra parameters attached to them, which would be very awkward to use and make overloaded operators (e.g. <$> or >>=) impossible. Personally I'm not a big fan of that approach, but it's a thing people have explored a lot. Check out this article for example.
          – Fyodor Soikin
          Nov 13 '18 at 19:48












          Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
          – Dax Fohl
          Nov 13 '18 at 20:00




          Yeah I was thinking about that one. Though I think there might be a way that combines the advantages of both, but requiring a new language. Still plotting.
          – Dax Fohl
          Nov 13 '18 at 20:00













          1














          Folloing @FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
          Finally we will be able to build an Array of records which will be "isomorphic" to:



          exists tail. Array { x :: Int | tail }


          Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.



          newtype Exists f = Exists (forall a. f (a :: #Type))


          We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:



          module Main where

          import Prelude

          import Unsafe.Coerce (unsafeCoerce)
          import Data.Newtype (class Newtype, unwrap)

          newtype Exists f = Exists (forall a. f (a :: #Type))

          mkExists :: forall f a. f a -> Exists f
          mkExists r = Exists (unsafeCoerce r :: forall a. f a)

          runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
          runExists g (Exists f) = g f


          Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:



          newtype R t = R { x :: Int | t }
          derive instance newtypeRec :: Newtype (R t) _


          Now we can build an Array using mkExists:



          arr :: Array (Exists R)
          arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]


          and process values using runExists:



          x :: Array [ Int ]
          x = map (runExists (unwrap >>> _.x)) arr





          share|improve this answer























          • The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
            – Fyodor Soikin
            Nov 13 '18 at 23:24










          • I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
            – paluh
            Nov 13 '18 at 23:47












          • If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
            – Fyodor Soikin
            Nov 14 '18 at 0:17












          • I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
            – paluh
            Nov 14 '18 at 1:11
















          1














          Folloing @FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
          Finally we will be able to build an Array of records which will be "isomorphic" to:



          exists tail. Array { x :: Int | tail }


          Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.



          newtype Exists f = Exists (forall a. f (a :: #Type))


          We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:



          module Main where

          import Prelude

          import Unsafe.Coerce (unsafeCoerce)
          import Data.Newtype (class Newtype, unwrap)

          newtype Exists f = Exists (forall a. f (a :: #Type))

          mkExists :: forall f a. f a -> Exists f
          mkExists r = Exists (unsafeCoerce r :: forall a. f a)

          runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
          runExists g (Exists f) = g f


          Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:



          newtype R t = R { x :: Int | t }
          derive instance newtypeRec :: Newtype (R t) _


          Now we can build an Array using mkExists:



          arr :: Array (Exists R)
          arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]


          and process values using runExists:



          x :: Array [ Int ]
          x = map (runExists (unwrap >>> _.x)) arr





          share|improve this answer























          • The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
            – Fyodor Soikin
            Nov 13 '18 at 23:24










          • I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
            – paluh
            Nov 13 '18 at 23:47












          • If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
            – Fyodor Soikin
            Nov 14 '18 at 0:17












          • I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
            – paluh
            Nov 14 '18 at 1:11














          1












          1








          1






          Folloing @FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
          Finally we will be able to build an Array of records which will be "isomorphic" to:



          exists tail. Array { x :: Int | tail }


          Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.



          newtype Exists f = Exists (forall a. f (a :: #Type))


          We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:



          module Main where

          import Prelude

          import Unsafe.Coerce (unsafeCoerce)
          import Data.Newtype (class Newtype, unwrap)

          newtype Exists f = Exists (forall a. f (a :: #Type))

          mkExists :: forall f a. f a -> Exists f
          mkExists r = Exists (unsafeCoerce r :: forall a. f a)

          runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
          runExists g (Exists f) = g f


          Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:



          newtype R t = R { x :: Int | t }
          derive instance newtypeRec :: Newtype (R t) _


          Now we can build an Array using mkExists:



          arr :: Array (Exists R)
          arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]


          and process values using runExists:



          x :: Array [ Int ]
          x = map (runExists (unwrap >>> _.x)) arr





          share|improve this answer














          Folloing @FyodorSoikin answer based on "existential types" and what we can find in purescript-exists we can provide yet another solution.
          Finally we will be able to build an Array of records which will be "isomorphic" to:



          exists tail. Array { x :: Int | tail }


          Let's start with type constructor which can be used to existentially quantify over a row type (type of kind #Type). We are not able to use Exists from purescript-exists here because PureScript has no kind polymorphism and original Exists is parameterized over Type.



          newtype Exists f = Exists (forall a. f (a :: #Type))


          We can follow and reimplement (<Ctrl-c><Ctrl-v> ;-)) definitions from Data.Exists and build a set of tools to work with such Exists values:



          module Main where

          import Prelude

          import Unsafe.Coerce (unsafeCoerce)
          import Data.Newtype (class Newtype, unwrap)

          newtype Exists f = Exists (forall a. f (a :: #Type))

          mkExists :: forall f a. f a -> Exists f
          mkExists r = Exists (unsafeCoerce r :: forall a. f a)

          runExists :: forall b f. (forall a. f a -> b) -> Exists f -> b
          runExists g (Exists f) = g f


          Using them we get the ability to build an Array of Records with "any" tail but we have to wrap any such a record type in a newtype before:



          newtype R t = R { x :: Int | t }
          derive instance newtypeRec :: Newtype (R t) _


          Now we can build an Array using mkExists:



          arr :: Array (Exists R)
          arr = [ mkExists (R { x: 8, y : "test"}), mkExists (R { x: 9, z: 10}) ]


          and process values using runExists:



          x :: Array [ Int ]
          x = map (runExists (unwrap >>> _.x)) arr






          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 13 '18 at 21:51

























          answered Nov 13 '18 at 21:39









          paluhpaluh

          1,5711511




          1,5711511












          • The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
            – Fyodor Soikin
            Nov 13 '18 at 23:24










          • I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
            – paluh
            Nov 13 '18 at 23:47












          • If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
            – Fyodor Soikin
            Nov 14 '18 at 0:17












          • I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
            – paluh
            Nov 14 '18 at 1:11


















          • The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
            – Fyodor Soikin
            Nov 13 '18 at 23:24










          • I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
            – paluh
            Nov 13 '18 at 23:47












          • If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
            – Fyodor Soikin
            Nov 14 '18 at 0:17












          • I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
            – paluh
            Nov 14 '18 at 1:11
















          The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
          – Fyodor Soikin
          Nov 13 '18 at 23:24




          The problem with purescript-exists is, you can't constrain the type. If the type needed to be constrained (e.g. like Show in my answer), the constraint would have to be baked into the definition of Exists, which pretty much defeats the idea of it being a reusable library. And this is in addition to the other problem of it not being kind-polymorphic, which you had to work around with copy-paste.
          – Fyodor Soikin
          Nov 13 '18 at 23:24












          I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
          – paluh
          Nov 13 '18 at 23:47






          I'm not sure if I understands your comment in this context @FyodorSoikin. * In original question @DaxFohl asked how to create an collection of records which share some known fields which is clearly done in my answer. We don't have to drag any constraint with this representation as we have direct access to known values. * The Lack of kind polymorphism is not a big problem because Exists has to be provided once per kind (so for example we now can reuse this Exists for every type which is parametrized over row).
          – paluh
          Nov 13 '18 at 23:47














          If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
          – Fyodor Soikin
          Nov 14 '18 at 0:17






          If the OP only cared about the value of the x field, then he could just make an array of those values - there would be no need to make the array heterogenous, no need for Exists or anything else. The only reason to have a heterogenous array is to get access to the specific types of elements later in one way or another, and Exists does not do that.
          – Fyodor Soikin
          Nov 14 '18 at 0:17














          I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
          – paluh
          Nov 14 '18 at 1:11




          I think that in general you are right but there are other possible reasons to build such an array like avoiding coping (when large parts of records are common) or debugging purposes and probably many others. Who knows what the original intent of OP was - the question was strictly about building an array ;-) Regarding Exists I'm finding it really useful for modeling GADTs but this is another topic I think...
          – paluh
          Nov 14 '18 at 1:11


















          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.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • 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%2f53270182%2fsimilar-record-types-in-a-list-array-in-purescript%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

          Florida Star v. B. J. F.

          Error while running script in elastic search , gateway timeout

          Adding quotations to stringified JSON object values