User Defined Record Type in CAL?
A simple question I suspect. I have the simple function definition
makePatientFixture :: [ { name :: String, age :: Int} ];
makePatientFixture = [ { name = "Dave", age = 41}, { name = "Denise", age = 45}, { name = "Cameron", age = 5} ];
I actually want to define a new type called
Patient = { name :: String, age :: Int }
This would mean that I don't have to repeat the record structure all of the time ({ name :: String, age :: Int }) instead my code would look like:
makePatientFixture :: [ Patient ];
makePatientFixture = [ { name = "Dave", age = 41}, { name = "Denise开发者_运维技巧", age = 45}, { name = "Cameron", age = 5} ];
Is this possible? Does it make sense from a CAL perspective (it may not)?
CAL does not support aliasing (which Haskell does with the 'type' keyword), so you can't just do:
type Patient = {name::String, age::Int}
However, you can create a newdata type that incorporates your record:
data Patient=
public Patient
details :: {name::String, age::Int}
;
... however, this is probably not what you need. Records as very handy for moving around bits of structured data and using a structural polymorphism (automatic projection of record subsets). You don't need to store the data like this though. Instead, I'd recommend:
data Patient=
public Patient
name :: !String
age :: !Int
;
The 'plings' on the types mean 'don't bother storing a lazy thunk here', e.g. we really want a string and and int even if you apply some complicated expression to the Patient constructor. You can safely omit the plings, but it's good practice to include them in most cases.
You can now use various forms of case analysis to extract elements from such a Patient value. You'll see all these in the manual, but here's a summary:
Overt case analysis, positional match:
age p =
case p of
Patientname age -> age; // Spot the maintenance problem here!
;
Overt case analysis, symbol match:
nameAndAge p =
case p of
Patient{name,age} -> (name,age); // Now it doesn't matter if the Patient constructor grows new arguments
;
Lazy extractor
name p =
let
Patient{name} = p; // name is only extracted (and p partially evaluated) if name is required
in
name;
Single case extractor
name p =
p.Patient.name; // Syntactic sugar for example 1. Useful when you are _only_ extracting a single field.
You can always project a record from this data if you need to. Remember you can also have multiple constructors for the Patient data type, if there are several kinds of Patient.
For example, perhaps there are in-patients and out-patients. Both of these share some nhs patient records, but have specific fields pertinent to their treatment. We could represent along the following lines:
data Patient=
public InPatient
patientRecords :: !PatientRecord
careCentreID :: !Int
department :: !String
consultant :: !String
| publicOutPatient
patientRecords :: !PatientRecord
appointments :: ![Appointment]
;
nhsRecords p =
case p of
(InPatient|OutPatient) {patientRecords} -> patientRecords;
;
This also allows us to look at a very powerful CAL feature that does multiple constructor matching. In this case we match InPatient and OutPatient, projecting only the patientRecords field.
This allows us to write an 'nhsRecords' extractor function that we can maintain fairly easily even as the details in the Patient constructors change.
Indeed, unless constructors come and go, or something happens to the "patientRecords" field itself, the this function need never change.
精彩评论