Purescript-heterogeneous: maps and folds for heterogeneous data types

I’ve just released an initial version of https://github.com/natefaubion/purescript-heterogeneous which aims to make writing maps and folds over stuff like Record a lot easier and more approachable. I’ve written up a short README which covers the basics of using it (with Records at least), and I’ve included some examples in the tests. One is an implementation of HList which supports hfoldlWithIndex with Peano numerals for indexing.

Some interesting points/issues:

  • Never write boilerplate Row.Cons instances again! :laughing:
  • The fundeps are very flexible (and thus inference may not be as good). One example is that you can write type-aligned folds where the accumulator type changes with every step.
  • Inference is weird for the bare function case, so you have to make sure its monomorphic. Maybe related to fundeps?

Anyway, I’d love to hear thoughts and feedback.

3 Likes

Looks like I should bring some of this into Formless – so many boilerplate classes for various transformations. I’ll give it a try when I have some time – probably this weekend – and give you some feedback as I work!

1 Like

I tried using this but I failed, probably because of my lack of knowledge with the concepts used.

What I wanted to do was (I was actually using it with Formless!) this kind of transformation:

{foo :: { input :: a }, bar :: { input :: b }, baz :: { input :: c } } -> {foo :: a, bar :: b, baz :: c}

I tried using FoldingWithIndex and Record.build. I had to modify FoldingWithIndex to add a functional dependecy f -> y, but after a while I got stuck on other errors and since I was mostly trying stuff at random I gave up :smiley:

I’m pasting here the messy code:

data GetInput = GetInput

instance getInput
  :: ( IsSymbol sym
     , Row.Cons sym b a c
     , Row.Lacks sym a
     )
  => FoldingWithIndex GetInput (SProxy sym) (Record a) (Record (input :: b)) (Record c) where
    foldingWithIndex GetInput prop record a =
      Builder.build (Builder.insert prop a.input) record

getInput' :: forall a r t. HFoldlWithIndex GetInput (Record a) { | r } (Record a)
  => RowToList a t => { | r} -> Record a
getInput' r = hfoldlWithIndex GetInput ??? r -- <- no idea how to make this work

testGetInput :: { a :: Int, b :: String }
testGetInput =
  getInput' { a: { input: 3 }, b: { input: "foo" } }

You don’t need to use a fold since you are applying the same transformation to every field. You can do this with hmap.

newtype GetProp (prop :: Symbol) = GetProp (SProxy prop)

instance getProp ::
  (IsSymbol prop, Row.Cons prop a rx r) =>
  Mapping (GetProp prop) { | r } a where
  mapping (GetProp prop) = Record.get prop

getInput :: forall prop rin rout.
  HMap (GetProp "input") { | rin } { | rout } =>
  { | rin } ->
  { | rout }
getInput = hmap (GetProp (SProxy :: SProxy "input"))

I haven’t compiled this, but it should mostly be there :smiley:

It works perfectly! I wish I kept my map attempt to see what I was getting wrong :slight_smile:

Thanks a lot

1 Like

@dariooddenino I see that you have built some nice usage example for heterogeneous so I allow myself to post here a link to your lib:

https://github.com/dariooddenino/purescript-record-prefix

2 Likes