Given an example data type with record syntax:
data VmInfo = VmInfo {infoVid :: String
,infoIndex :: Int
,infoPid :: Int
,infoExe :: String
} deriving (Show)
and (vmInfo :: String -> VmInfo) function that generates and returns the above data structure given vm name as string.
I can see two methods to extract the individual parts of the VmInfo data type.
(VmInfo vid _ _ _) <- vmInfo vm
Which is just a pattern match. And …
vid <- infoVid <$> vmInfo vm
using record syntax compiler generated functions.
The question is simple: which is a preferred method?
Amount-of-typing wise they are the same so I am looking for speed and correctness/best practice.
I assume the pattern matching would be faster but then what is the point of record syntax?
These aren’t semantically equivalent.
Let’s look at the first example:
This performs a pattern match in the binding operation. There are two results of this. The first is that the constructor of the result of the
vmInfo vmaction is evaluated. This means that ifvmInfoended with a line likereturn undefined, the exception thrown by evaluatingundefinedwould happen at this pattern match, not a later use ofvid. The second is that if the pattern match is refuted (the pattern match does not match the value), the monad’sfailinstance will be called with the pattern match error text. That’s not possible in this case, but it is generally possible when pattern matching a constructor in a bind.Now, on to the next example:
By the definition of
<$>, this will be entirely lazy in the value returned by the action (not the effects). IfvmInfoended withreturn undefined, you wouldn’t get the exception from evaluatingundefineduntil you did something that used the value ofvid. Additionally, ifinfoVoidhad the ability to throw any exceptions, they wouldn’t end up happening until the use ofvid, best case.Interestingly enough, these differences are only present in the scope of a monadic bind. If
vmInfowas pure and you were binding the namevidinside aletorwhereexpression, they would generate identical code.In that case, which one you would want to use is entirely up to you. Both are idiomatic Haskell. People generally pick whichever looks better in the context they’re working in.
The main reasons people use accessor functions is brevity when the record has so many fields a pattern match is huge, and because they are actual functions – they can be passed to any higher-order function their type fits into. You can’t pass around pattern matches as a distinct construct.