To put a term on this, it might be the same thing as “defunctionalization”! (Links: 1, 2, 3)
So I think you’re on the right track, and I think you actually do have a way to convert a -> String
: passing the right function down from the nesting context ought to do it. For example, you can tell it to represent the discrete comparisons by supplying String.fromInt
or String.fromFloat
:
toString : FilterField -> String
toString filterField =
case filterField of
FilterId comparison ->
"filtered by ID " ++ discreteComparisonToString String.fromInt comparison
FilterName comparison ->
"filtered by name " ++ discreteComparisonToString identity comparison
-- the rest of your constructors here
discreteComparisonToString : (a -> String) -> DiscreteComparison a -> String
discreteComparisonToString stringify discreteComparison =
case discreteComparison of
Eq a ->
"equal to " ++ stringify a
In values ->
"in " ++ String.join ", " (List.map stringify values)
Now you can get:
In | Out |
---|---|
FilterId (In [ 1, 2, 3 ]) |
filtered by ID in 1, 2, 3 |
FilterName (Eq "Dave") |
filtered by name equal to Dave |
If you want to convert this to HTML instead, you can make:
toHtml : FilterField -> Html Msg
toHtml filterField =
case filterField of
FilterId comparison ->
Html.div []
[ Html.text (toString filterField)
, Html.button
[ Events.onClick (RemoveFilter filterField) ]
[ Html.text "Remove Filter" ]
]
-- et cetera
editing is a little tricker, but Html.map
may come in handy for wrapping and unwrapping the inner types. You can then represent interactivity with an arbitrary HTML structure.