How to model filter conditions as data

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.

2 Likes