Search code examples
haskellinitializationrecord

Force keyword arguments in Haskell record init


Can I force the use of keywords only when initializing a record in Haskell?

data Person
   = Person
   {
       name :: String,
       idnum :: String
   }
   deriving( Show )


main = putStrLn $ show $ Person "oren" "9200"

-- I want to only allow such initializations:
-- main = putStrLn $ show $ Person { name = "moish", idnum = "7400" }

(This is especially useful when two fields have the same type)


Solution

  • As far as I know, no.

    Consider maybe the following solution?

    newtype Name = Name String
    newtype Idnum = Idnum String
    
    data Person = Person { name :: Name, idnum :: Idnum }
    

    Another possibility, worse in my opinion, is this:

    module A (Person, name, idnum) where
    
    data Person = Person
      { _name :: String
      , _idnum :: String
      }
    
    name :: String -> (Person -> Person)
    name n p = p { _name = n }
    
    idnum :: String -> (Person -> Person)
    idnum n p = p { _idnum = n }
    
    emptyPerson :: Person
    emptyPerson = Person "" ""
    
    # in another module
    
    module B
    
    import A (Person, name, idnum)
    
    myPerson = name "myname" . idnum "myidnum" $ emptyPerson
    

    In this case there's no guarantee that both name and idnum get a value.


    The fact that Person can always be used as a 'plain function' may turn out to be very useful. If you have, for instance, getName :: IO String and getIdnum :: IO String, then combining these to form a getPerson :: IO Person is concise: getPerson = Person <$> getName <*> getIdnum. This is only possible because we don't use record syntax here!