1
+ {-# LANGUAGE DuplicateRecordFields #-}
2
+ {-# LANGUAGE OverloadedRecordDot #-}
3
+ {-# LANGUAGE NoFieldSelectors #-}
4
+
1
5
module Lesson4DataTypes where
2
6
7
+ import Data.Time.Clock (UTCTime )
8
+
3
9
-- Algebraic Data Types!
4
10
5
11
-- Sum types: like enums
@@ -12,23 +18,23 @@ data Bool' = True' | False'
12
18
-- Product types: combine different data
13
19
-- compare to tuples in Lesson3: feI :: (String, Float)
14
20
type Wavelength = Float
15
- data SpectralLine = SpectralLine Element Wavelength
21
+ data SpectralLine1 = SpectralLine1 Element Wavelength
16
22
17
- lines :: [SpectralLine ]
23
+ lines :: [SpectralLine1 ]
18
24
lines = [heI, feI, ha, caII_854]
19
25
where
20
- heI = SpectralLine He 10830
21
- feI = SpectralLine Fe 6302
22
- ha = SpectralLine H 6562
23
- caII_854 = SpectralLine Ca 8542
26
+ heI = SpectralLine1 He 10830
27
+ feI = SpectralLine1 Fe 6302
28
+ ha = SpectralLine1 H 6562
29
+ caII_854 = SpectralLine1 Ca 8542
24
30
25
31
-- We can combine the two to handle complex states!
26
32
data HasElement = None | Has Element
27
33
28
34
-- (Note: this is exactly how Maybe works, just with a type variable)
29
35
-- data Maybe a = Nothing | Just a
30
36
31
- -- *** MAKE IMPOSSIBLE STATES IMPOSSIBLE! ***
37
+ -- *** MAKE IMPOSSIBLE STATES IMPOSSIBLE (to represent) ! ***
32
38
33
39
-- Example 1. Wait, are we working in Angstroms or Nanometers?
34
40
pleaseDontCallWithAngstroms :: Wavelength -> Float
@@ -59,20 +65,20 @@ badLineName :: SpectralLineBad2 -> String
59
65
-- what is the name of Ca Nothing?
60
66
badLineName (SpectralLineBad2 He Nothing ) = " Helium"
61
67
badLineName (SpectralLineBad2 H Nothing ) = " Hydrogen"
62
- badLineName (SpectralLineBad2 Fe Nothing ) = " Iron"
63
- badLineName (SpectralLineBad2 Ca (Just CaII_849 )) = " Calcium 849"
64
- badLineName (SpectralLineBad2 Ca (Just CaII_852 )) = " Calcium 849 "
65
- badLineName (SpectralLineBad2 Ca (Just CaII_866 )) = " Calcium 849 "
68
+ badLineName (SpectralLineBad2 Fe Nothing ) = " Iron I "
69
+ badLineName (SpectralLineBad2 Ca (Just CaII_849 )) = " Calcium II 849"
70
+ badLineName (SpectralLineBad2 Ca (Just CaII_852 )) = " Calcium II 852 "
71
+ badLineName (SpectralLineBad2 Ca (Just CaII_866 )) = " Calcium II 866 "
66
72
67
73
-- Better: create an ADT that models the states exactly
68
- data SpectralLine'
74
+ data SpectralLine
69
75
= HeliumI
70
76
| HydrogenAlpha
71
77
| IronI
72
78
| CalciumII CaIILine
73
79
74
- lines' :: [SpectralLine' ]
75
- lines' =
80
+ allLines :: [SpectralLine ]
81
+ allLines =
76
82
[ HeliumI
77
83
, HydrogenAlpha
78
84
, IronI
@@ -84,16 +90,177 @@ lines' =
84
90
-- Instead of putting the wavelength and element in the datatype, now we can calculate it from the Line
85
91
-- Impossible to represent Helium + a calcium band
86
92
-- Or calcium without one
87
- element :: SpectralLine' -> Element
93
+ element :: SpectralLine -> Element
88
94
element HeliumI = He
89
95
element HydrogenAlpha = H
90
96
element IronI = Fe
91
97
element (CalciumII _) = Ca
92
98
93
- wavelength :: SpectralLine' -> Float
94
- wavelength HeliumI = 10830
95
- wavelength HydrogenAlpha = 6562
96
- wavelength IronI = 6302
97
- wavelength (CalciumII CaII_849 ) = 8498
98
- wavelength (CalciumII CaII_852 ) = 8542
99
- wavelength (CalciumII CaII_866 ) = 8662
99
+ wavelength :: SpectralLine -> Nanometers
100
+ wavelength HeliumI = Nanometers 108.30
101
+ wavelength HydrogenAlpha = Nanometers 656.2
102
+ wavelength IronI = Nanometers 630.2
103
+ wavelength (CalciumII CaII_849 ) = Nanometers 849.8
104
+ wavelength (CalciumII CaII_852 ) = Nanometers 854.2
105
+ wavelength (CalciumII CaII_866 ) = Nanometers 866.2
106
+
107
+ -- Records: let you name fields
108
+ data SpatialPixel = SpatialPixel
109
+ { x :: Int
110
+ , y :: Int
111
+ , luminosity :: Float
112
+ }
113
+
114
+ data SpatialImage = SpatialImage
115
+ { wavelength :: Nanometers
116
+ , pixels :: [SpatialPixel ]
117
+ }
118
+
119
+ -- Use "." to access fields
120
+ findVerticalLine :: Int -> SpatialImage -> [Float ]
121
+ findVerticalLine y img =
122
+ map (. luminosity) $ filter (\ px -> px. y == y) img. pixels
123
+
124
+ -- TODO: Exercise: Model an observation frame for the various instruments.
125
+ -- This model forces us to handle weird edge cases deep in our code!
126
+ -- Improve it as much as possible
127
+ data ObservationFrame = ObservationFrame
128
+ { observationId :: String
129
+ , proposalId :: String
130
+ , dateTime :: String
131
+ , instrument :: String
132
+ , -- ViSP has 0 spatial images
133
+ -- VBI has N spatial images
134
+ -- Cryo has 1 spatial image
135
+ spatialImages :: [SpatialImage ]
136
+ , -- ViSP has 3 spectral images
137
+ -- Cryo has 1
138
+ -- VBI has 0
139
+ spectralImage1 :: Maybe SpectralImage
140
+ , spectralImage2 :: Maybe SpectralImage
141
+ , spectralImage3 :: Maybe SpectralImage
142
+ }
143
+
144
+ -- TODO: define SpectralImage
145
+ data SpectralImage = SpectralImage
146
+
147
+ -- TODO: Fix me!
148
+ -- Why is this problematic?
149
+ -- If we model the data poorly we have to check for data issues every time we access them
150
+ -- And we keep having to deal with edge conditions everywhere!
151
+ processCryo :: [ObservationFrame ] -> [String ]
152
+ processCryo frames =
153
+ let cfs = filter isCryo frames
154
+ wls = map cryoSpatialWavelength cfs
155
+ in map handleWavelength wls
156
+ where
157
+ isCryo f = f. instrument == " Cryo"
158
+
159
+ handleWavelength :: Maybe Nanometers -> String
160
+ handleWavelength Nothing = " ERROR, Cryo missing wavelength!"
161
+ handleWavelength (Just (Nanometers wl)) = " Looks good: " ++ show wl
162
+
163
+ cryoSpatialWavelength :: ObservationFrame -> Maybe Nanometers
164
+ cryoSpatialWavelength frame =
165
+ case frame. spatialImages of
166
+ [img] -> Just img. wavelength
167
+ -- TODO: what do we do if a cryo frame doesn't have an image?
168
+ [] -> Nothing
169
+ -- TODO: what do we do if a cryo frame has multiple images?
170
+ imgs -> Nothing
171
+
172
+ --
173
+ --
174
+ --
175
+ --
176
+ --
177
+ --
178
+ --
179
+ --
180
+ --
181
+ --
182
+ --
183
+ --
184
+ --
185
+ --
186
+ --
187
+ --
188
+ --
189
+ --
190
+ --
191
+ --
192
+ --
193
+ --
194
+ --
195
+ --
196
+ --
197
+ --
198
+ --
199
+ --
200
+ --
201
+ --
202
+ --
203
+ --
204
+ --
205
+ --
206
+ --
207
+ --
208
+ --
209
+ --
210
+ --
211
+ --
212
+ --
213
+ --
214
+ --
215
+ --
216
+ --
217
+ --
218
+ --
219
+ --
220
+ --
221
+ -- DONE: Solution
222
+ -- (note) the ticks after names (like Instrument') are to avoid conflicts with definitions
223
+ -- you might write during your solution. This is common practice in haskell when you have a
224
+ -- variation on a type
225
+
226
+ newtype ObservationId' = ObservationId' String
227
+ newtype ProposalId' = ProposalId' String
228
+
229
+ data Instrument'
230
+ = Cryo' SpatialImage SpectralImage
231
+ | VBI' [SpatialImage ]
232
+ | ViSP' ViSPObservation -- It can be useful to have a record with named fields
233
+
234
+ data ViSPObservation = ViSPObservation
235
+ { arm1 :: SpectralImage
236
+ , arm2 :: SpectralImage
237
+ , arm3 :: SpectralImage
238
+ }
239
+
240
+ data ObservationFrame' = ObservationFrame'
241
+ { observationId :: ObservationId'
242
+ , proposalId :: ProposalId'
243
+ , dateTime :: UTCTime
244
+ , instrument :: Instrument'
245
+ }
246
+
247
+ processAll :: [ObservationFrame' ] -> [String ]
248
+ processAll frames =
249
+ map processFrame $ map (. instrument) frames
250
+ where
251
+ processFrame :: Instrument' -> String
252
+ processFrame (Cryo' spatial spectral) = processCryo spatial spectral
253
+ processFrame (VBI' imgs) = processVBI imgs
254
+ processFrame (ViSP' visp) = processViSP visp
255
+
256
+ processCryo :: SpatialImage -> SpectralImage -> String
257
+ processCryo img _ = handleWavelength img. wavelength
258
+
259
+ processVBI :: [SpatialImage ] -> String
260
+ processVBI _ = " processed VBI differently!"
261
+
262
+ processViSP :: ViSPObservation -> String
263
+ processViSP _ = " processed VISP differently!"
264
+
265
+ handleWavelength :: Nanometers -> String
266
+ handleWavelength (Nanometers wl) = " Looks good: " ++ show wl
0 commit comments