Hi!
I’ve noticed a pretty common (and a bit tedious) pattern in my tables: I have an array of records, and I want to be able to sort them by each field (without any kind of async server interaction).

Right now I’m doing this in a very clumsy way, by defining a sum type SortBy, and then using it to to build the table header, as an argument in the sort function, etc., but I’d like to be able to derive most of this stuff from types.

I got somewhere, but I need some directions, or maybe just to know if I’m on the right path and if this is possible.

This is what I have right now:

data SortOrder = Asc | Desc
derive instance eqSortOrder :: Eq SortOrder
flipSort :: SortOrder -> SortOrder
flipSort Asc = Desc
flipSort Desc = Asc
type SortBy sym = SProxy sym
data ArraySort a = ArraySort (SortBy a) SortOrder
sortResults ::
forall sym a r' r.
IsSymbol sym =>
Row.Cons sym a r' r =>
Ord a =>
ArraySort sym ->
Array { | r } ->
Array { | r }
sortResults (ArraySort sB sO) elems =
let res = sortWith (Record.get sB) elems
in case sO of
Asc -> res
Desc -> reverse res
data BuildM fn = BuildM fn
instance buildM_ ::
(Monoid m, IsSymbol sym) =>
FoldingWithIndex (BuildM (a -> m)) (SProxy sym) m a m where
foldingWithIndex (BuildM fn) prop arr a = fn a <> arr
buildM ::
forall a m r.
Monoid m =>
HFoldlWithIndex (BuildM (a -> m)) m { | r } m =>
(a -> m) ->
{ | r } ->
m
buildM fn r = hfoldlWithIndex (BuildM fn) mempty r

I have a function to sort my array given a SProxy and a tentative function that when complete will build Halogen HTML.

The problem is that this function needs to have access to the SProxy too, both to render the label and to call sortResults with the correct value.
If I change (a -> m) to (SProxy sym -> a -> m) it works, but I’m only allowed to have one label in my record obviously

@dariooddenino, the sortResults function looks like a good start, but I don’t understand its relation to the buildM function. From what I see, the buildM function folds a record by accumulating its values in an Array, no? Do you intend to evolve buildM into a function which generates Halogen markup?

I can’t clearly see the exact problem you’re trying to solve. So… I’ll just write some pseudocode which might help you. I’m presuming the Monoid is the Halogen markup you mention above.

data SortOrder = Asc | Desc
derive instance eqSortOrder :: Eq SortOrder
data SortConfig fieldName = SortConfig (SProxy fieldName) SortOrder
sortResults ::
Row.Cons field val r' r =>
Ord val =>
IsSymbol field =>
SortConfig field ->
Array (Record r) ->
Array (Record r)
sortResults (SortConfig fieldName order) recs =
case order of
Asc -> sortedRecs
Desc -> reverse sortedRecs
where
sortedRecs = sortWith (Record.get (reflectSymbol fieldName)) recs
-- A record fold instance which creates a table header from a record.
data TableHeader = TableHeader
instance tableHeaderFolding ::
(Monoid m, IsSymbol sym) =>
FoldingWithIndex TableHeader (SProxy sym) m a m where
foldingWithIndex TableHeader prop accum _ =
(tableHeaderElement (reflectSymbol prop))
-- A record fold instance which creates a table row from a record,
-- if the record's values have a render function defined (RenderRowVal).
data TableRow = TableRow
instance tableRowFolding ::
(Monoid m, IsSymbol sym, RenderRowVal a m) =>
FoldingWithIndex TableRow (SProxy sym) m a m where
foldingWithIndex TableRow _ accum val =
(tableRowElement (renderRowVal val))
recsToTable ::
Monoid m =>
HFoldlWithIndex TableHeader m (Record r) m =>
HFoldlWithIndex TableRow m (Record r) m =>
Array (Record r) ->
m
recsToMarkup recs = tableElement (tableHeader <> tableRows)
where
tableHeader :: m
-- Just need one record for `foldingWithIndex` to get the type info.
tableHeader = foldingWithIndex TableHeader mempty (unsafePartial head recs)
tableRows :: m
tableRows = foldMap recToRow recs
recToRow :: Record r -> m
recToRow rec = foldingWithIndex TableRow mempty rec
sortAndRenderTable ::
Monoid m =>
SortConfig ->
Array (Record r) ->
m
sortAndRenderTable sortConfig recs = recsToMarkup (sortResults sortConfig recs)

Thanks for answering and sorry for net being enough clear!

The two functions are not directly related; I just want to use them together with the goal of handling my tables.

Yes, I wanted buildM to eventually become a function that could build Halogen markup, given a function fn :: (SProxy sym -> a -> HH.HTML) and a Record, so that I could have used it to build both header and rows cells.

The problem with my approach is that, as I wrote in the other message, it works only for records with one label. I’m still on shaky grounds when doing type level programming, so I never know if I’m trying to do something impossibile or if I’m just using the wrong approach.

I will check your solution once I’m back at the office, thanks again!