Skip to content

Commit 79a3dc5

Browse files
committed
Lesson4
1 parent 6c5306b commit 79a3dc5

File tree

3 files changed

+191
-22
lines changed

3 files changed

+191
-22
lines changed

app/Lesson4DataTypes.hs

Lines changed: 189 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
{-# LANGUAGE DuplicateRecordFields #-}
2+
{-# LANGUAGE OverloadedRecordDot #-}
3+
{-# LANGUAGE NoFieldSelectors #-}
4+
15
module Lesson4DataTypes where
26

7+
import Data.Time.Clock (UTCTime)
8+
39
-- Algebraic Data Types!
410

511
-- Sum types: like enums
@@ -12,23 +18,23 @@ data Bool' = True' | False'
1218
-- Product types: combine different data
1319
-- compare to tuples in Lesson3: feI :: (String, Float)
1420
type Wavelength = Float
15-
data SpectralLine = SpectralLine Element Wavelength
21+
data SpectralLine1 = SpectralLine1 Element Wavelength
1622

17-
lines :: [SpectralLine]
23+
lines :: [SpectralLine1]
1824
lines = [heI, feI, ha, caII_854]
1925
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
2430

2531
-- We can combine the two to handle complex states!
2632
data HasElement = None | Has Element
2733

2834
-- (Note: this is exactly how Maybe works, just with a type variable)
2935
-- data Maybe a = Nothing | Just a
3036

31-
-- *** MAKE IMPOSSIBLE STATES IMPOSSIBLE! ***
37+
-- *** MAKE IMPOSSIBLE STATES IMPOSSIBLE (to represent)! ***
3238

3339
-- Example 1. Wait, are we working in Angstroms or Nanometers?
3440
pleaseDontCallWithAngstroms :: Wavelength -> Float
@@ -59,20 +65,20 @@ badLineName :: SpectralLineBad2 -> String
5965
-- what is the name of Ca Nothing?
6066
badLineName (SpectralLineBad2 He Nothing) = "Helium"
6167
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"
6672

6773
-- Better: create an ADT that models the states exactly
68-
data SpectralLine'
74+
data SpectralLine
6975
= HeliumI
7076
| HydrogenAlpha
7177
| IronI
7278
| CalciumII CaIILine
7379

74-
lines' :: [SpectralLine']
75-
lines' =
80+
allLines :: [SpectralLine]
81+
allLines =
7682
[ HeliumI
7783
, HydrogenAlpha
7884
, IronI
@@ -84,16 +90,177 @@ lines' =
8490
-- Instead of putting the wavelength and element in the datatype, now we can calculate it from the Line
8591
-- Impossible to represent Helium + a calcium band
8692
-- Or calcium without one
87-
element :: SpectralLine' -> Element
93+
element :: SpectralLine -> Element
8894
element HeliumI = He
8995
element HydrogenAlpha = H
9096
element IronI = Fe
9197
element (CalciumII _) = Ca
9298

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

learn-haskell.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ executable learn-haskell
3434
build-depends:
3535
base >=4.16
3636
, text
37+
, time
3738
default-language: GHC2021

package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ghc-options:
2121
dependencies:
2222
- base >= 4.16
2323
- text
24+
- time
2425

2526
executables:
2627
learn-haskell:

0 commit comments

Comments
 (0)