elena
elena

Reputation: 909

Add element to a list which is part of a type in Haskell

I am trying to add a new element of type Transaction as defined:

--Build type Transaction--
data Transaction = Transaction {
     start_state :: Int, 
     symbol :: Char, 
     end_state :: Int
} deriving Show

to the list of transactions which is part of the following type:

--Build type Automaton--
data Automaton = Automaton {
     initial_state :: Int, 
     states :: Set.Set Int,
     transactions :: [Transaction],
     final_states :: Set.Set Int
} deriving Show 

I have tried to implement the following function:

--Add to the automaton a transaction 
insert_transaction :: Transaction -> Automaton -> Automaton
insert_transaction t a = a{transactions =  t : transactions }

but it returns the following error:

automaton.hs:25:48: error:
    • Couldn't match expected type ‘[Transaction]’
                  with actual type ‘Automaton -> [Transaction]’
    • Probable cause: ‘transactions’ is applied to too few arguments
      In the second argument of ‘(:)’, namely ‘transactions’
      In the ‘transactions’ field of a record
      In the expression: a {transactions = t : transactions}
   |
25 | insert_transaction t a = a{transactions =  t : transactions }
   |                                                ^^^^^^^^^^^^

How can I implement this? Thank you!

Upvotes: 0

Views: 52

Answers (1)

leftaroundabout
leftaroundabout

Reputation: 120711

transactions is not really a value of type [Transaction], it's by itself only a record field accessor to a field of type [Transaction]. I.e., it's a function. GHCi could have told you:

Main*> :t transactions
transactions :: Automaton -> Transactions

So, to access those transactions, you need to be explicit what automaton from, by just giving it as an argument:

insertTransaction :: Transaction -> Automaton -> Automaton
insertTransaction t a = a{ transactions =  t : transactions a }

“By itself” meaning: when you use it in a normal Haskell expression without special record syntax like a{transactions = ...}. There, the transactions isn't really an independent entity at all.


There are a couple of alternatives you could consider:

  • You can “partially pattern match” on the fields of a record you pass as a function argument.

    insertTransaction t a{transactions = oldTransactions}
            = a{ transactions =  t : oldTransactions }
    
  • The RecordWildCards extension can bring an entire record's fields in scope as overloaded variables, somewhat like as if you're in an OO method of the class containing those fields. This allows you to write essentially what you had originally:

    {-# LANGUAGE RecordWildCards #-}
    insertTransaction t a{..} = a{ transactions =  t : transactions }        
    

    I would not recommend this: RecordWildCards is an unidiomatic hack around the limitations of Haskell98' record types.

  • Lenses are the modern and consequent way to use records in Haskell. You can get them most easily by slightly changing your data definition:

    {-# LANGUAGE TemplateHaskell #-}
    import Control.Lens
    data Automaton = Automaton {
       _initialState :: Int
     , _states :: Set.Set Int
     , _transactions :: [Transaction]
     , _finalStates :: Set.Set Int
     } deriving (Show)
    makeLenses ''Automaton  -- Generates `transactions` etc. not as plain accessor
                            -- functions, but as “optics” which can both get
                            -- and set field values.
    
    insertTransaction :: Transaction -> Automaton -> Automaton
    insertTransaction t = transactions %~ (t:)
    

Upvotes: 3

Related Questions