Justin Wood
Justin Wood

Reputation: 10041

Mandatory Maybes in the type system

I have something similar to the following

data A = A
  { id :: Integer
  , foo :: Maybe String
  , bar :: Maybe String
  , baz :: Maybe String
  }

This data is coming in to my service as JSON. This request is only considered valid when one or more of foo, bar, or baz are given. Is there a better way to express this within Haskell's type system?

Note: Unfortunately I am unable to make this separate requests. I'm just following a defined protocol.

Upvotes: 7

Views: 164

Answers (5)

Daniel Wagner
Daniel Wagner

Reputation: 152697

Consider giving one branch for each possible required field:

data A
    = Foo
        { foo :: String
        , barM, bazM :: Maybe String
        }
    | Bar
        { bar :: String
        , fooM, barM :: Maybe String
        }
    | Baz
        { baz :: String
        , fooM, barM :: Maybe String
        }

It's a fair bit of boilerplate, but it's very direct and quite clear about what's required.

Upvotes: 2

leftaroundabout
leftaroundabout

Reputation: 120711

Expanding on ʎǝɹɟɟɟǝſ's suggestion to use a map: there's also a type specifically for non-empty maps. (Note however that this sort of clashes with the more popular nonempty-list type from the semigroups library.)

import qualified Data.NonEmpty.Map as NEM

data Field = Foo | Bar | Baz
data A = A { id :: Integer
           , fields :: NEM.T Field String
           }

Upvotes: 2

Shoe
Shoe

Reputation: 76240

I would use a Map Field String with data Field = Foo | Bar | Baz (this can easily be replaced with String if needed, and then have:

data A = A
    { id :: Integer
    , fields :: Map Field String
    }

Now checking for the validity condition is as simple as:

isValid :: A -> Bool
isValid = not . Map.null . fields

Upvotes: 4

Markus1189
Markus1189

Reputation: 2869

If it is not mandatory to have three separate fields with foo,bar and baz, I'd go with this, NonEmpty guarantees that there is at least one element, though there can of course be more.

import Data.List.NonEmpty

data Impression = Banner String | Video String | Native String

data A = A
  { id :: Integer
  , fooBarBaz :: NonEmpty Impression
  }

Upvotes: 4

leftaroundabout
leftaroundabout

Reputation: 120711

http://hackage.haskell.org/package/these-0.4.2/docs/Data-These.html

import Data.These
data A = A
  { id :: Integer
  , fooBarBaz :: These Foo (These Bar Baz)
  }

type Foo = String
type Bar = String
type Baz = String

Upvotes: 11

Related Questions