Feedback for API to build data structure for opaque types

Hi all,

imagine the following types:

type Node
    = Node String (List Feature)


type Feature
    = Main (List Node)
    | Custom String (List Node)
    | Text (List Node)
    | None

I’m working on an API that allows users to build models with these types. However, I have, amongst others, the following requirement:

A node must not contain a Feature variant more than once. The exception is the Custom variant, where there can be 0…n, however, each Custom Feature must have a unique identifier (the first argument of type String of the Custom constructor).

To achieve that, I expose an API that let’s users add Nodes to the specific Feature.

-- exposed
underMain : Node -> Node -> Node
underMain (Node idParent features) child =
    Node idParent (addToMainFeature child features)

 
-- local   
addToMainFeature : Node -> List Feature -> List Feature
addToMainFeature child features =
    let
        (mainChildren, otherFeatures) =
            -- collects all children already under the Feature in question (Main) and puts them together with the new ones
            List.foldl 
                  (\feature (mChildren, featuresOther) ->
                      case feature of
                          Main children ->
                            (mChildren ++ children, featuresOther)
                      
                          _ ->
                            (mChildren, feature :: featuresOther)
                            
                    
                  )
                  ([child], [])
                  features
    in
      Main mainChildren :: otherFeatures

My implementation idea is to use foldl to collect all children that already exist under a certain Feature (e.g. the Main feature) prepend a new Main feature with all these children at the end. Neither the order of Features nor of Nodes matters at that point.

My specific questions are:

  • Is there a easier / better way to satisfy my requirement
  • I’d have to copy the addToXXXFeature function for the Text feature, and only change case of inside the anonymous function to match against the Text constructor. Can this be unified somehow? I’m thinking of a addToFeature : Feature -> Node -> List Feature -> List Feature function where the first parameter is the Feature to put the new node under. However, I don’t understand how I could pattern match the passed Feature variant to the one foldl is processing…?

Is this considered at a single level or in the whole tree? (E.g. can you have a Main with a sibling Text which contains another Main?)

Just a rough idea, but what about:

type Node
    = Node
        String
        { main = Maybe (List Node)
        , text = Maybe (List Node)
        , none = Maybe (List Node) -- (just including this for completeness as I don't know your domain… the name implies it could be excluded in this pattern)
        , custom = Dict String (List Node)
        }
1 Like

This requirement applies on each single node for now, so what you describe should be possible.

I like your idea, especially since the use of Maybe allows to have different semantics for a Nothing and a Just []. Thank you! I will give it a shot and report back if I encounter any problems, but my first feeling is that this would be much easier.
And yes, the “none” Feature can be left out, it was a leftover from an earlier design.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.